Getting started with Golang unit testing

Unit testing is a way of writing code to isolate individual units or components of a software application. These units can be standalone functions, methods, or entire packages. Unit testing aims to verify that each unit of code works correctly and behaves in a predetermined behavior.

This journey will examine how to test standalone functions and dependent packages. In Golang, we have to import a package for unit testing called testing, which enables us to automate the testing of Go packages.

For testing any module or package in Golang, we need to perform the following conventions to make the code executable:

  1. We create a file with the file’s name that contains our main logic, followed by _test.go. For example, to test the code of service.go, we create a file with the name, service_test.go. For testing the interface, we need to create a separate mock file to mock the interface.

  2. We need to follow the conventions for naming our test function, as shown below:

func Test<function to be test>(t *testing){} //For stand-alone function
func Test<dependent_type>_<function to be test>(t *testing){} //For type dependent function
  • func Test: This is mandatory for any test function.

  • <Function to be tested>: This is the name of the specific function that requires testing. If the function depends on a particular type, it’s a good practice to include the <dependent_type> name, although it’s not mandatory.

  • (t *testing): This is mandatory to pass the pointer of the testing type.

  1. To run the test, we need to run the command below:

go test # It will execute all the test functions

We can add -v flag to see the verbose output of test passes in the terminal. We can also add ./... to run all tests in the subdirectories.

Standalone-function unit testing

Standalone functions do not depend on external dependencies. Here, we test a simple function that calculates the sum of two numbers:

Note: Press the “Run” button and run the go test -v command in the integrated terminal below.

package main

import (
    "testing"
)

func TestSum(t *testing.T) {
    // Write test cases for sum function
    testCases := []struct {
        a        int
        b        int
        expected int
    }{
        {2, 3, 5},   // 2 + 3 = 5
        {0, 0, 0},   // 0 + 0 = 0
        {-3, 5, 2},  // -3 + 5 = 2
        {-2, -2, -4}, // -2 + (-2) = -4
    }

    for _, tc := range testCases {
        result := Sum(tc.a, tc.b)
        if result != tc.expected {
            t.Errorf("Sum(%d, %d) returned %d, expected %d", tc.a, tc.b, result, tc.expected)
        }
    }
}
Unit testing of the sum function

Explanation

  • Line 4: We import the testing package for unit testing.

  • Line 7: We create a test method signature for the Sum function by following the abovementioned convention.

  • Lines 9–8: We create the testCases that contains the input value (a, b) and the expected value.

  • Lines 20: We run the for loop to run all the test cases.

  • Line 21: We run the Sum function and store the value in result variable.

  • Line 22: We compare the result with the expected value.

Dependency-injection unit testing

Dependency injection is a technique used to provide dependencies to a service. This technique isolates the actual implementation of the main logic from its dependencies and is particularly useful for testing code by enabling mock dependencies.

We test a simple service that depends on the interface. Let’s suppose we have a Service that depends on an interface like ProductRepository. In actual implementation, this interface can be a database where all the products are stored. But for testing, we do not want to fetch the database; in this scenario, we mock the ProductRepository interface.

Note: Press the “Run” button and run the go test -v command in the integrated terminal below.

package main

import (
    "testing"
    "github.com/stretchr/testify/assert"
)

func TestProductService_GetProductByID(t *testing.T) {
    // Create a mock repository
    repo := NewMockProductRepository()
    service := NewService(repo)

    // Add a product to the mock repository
    product := Product{
        ID:    1,
        Name:  "Product 1",
        Price: 29.99,
    }
    repo.products[1] = product

    // Test case1: Retrieve the product
    retrievedProduct, err := service.GetProductByID(1)
    // Assert no error occur
    assert.NoError(t, err)

    // Check if the retrieved product matches the added product
    assert.Equal(t,retrievedProduct,product )

    // Test case2: Retrieve a non-existent product
    _, err = service.GetProductByID(2)
    assert.Error(t, err)
}
Example of dependency-injection unit testing

Explanation

First, we create a mock_productService.go file that contains a mock implementation of our dependency, ProductRepository. This mock implementation replicates the behavior of the GetProductByID function. When the GetProductByID function is called in productService_test.go, it invokes the corresponding mock implementation function defined in mock_productService.go:

  • Line 8: We create a test method signature for the GetProducByID function by following the convention discussed above.

  • Line 10: We create an instance of the MockproductRepository.

  • Line 11: We inject the mock dependency into our Service.

  • Lines 14–19: We add one product to the mock repository.

  • Line 22: We call the GetProductByID function for id 1 (test case 1: Existing element) and store the retrievedProduct variable.

  • Line 24: We use the assert that no error occurs by using the assert.NoError function from the "github.com/stretchr/testify/assert" package.

  • Line 27: We assert that the retrievedProduct is equal to the product.

  • Line 30: We call the GetProductByID function for id 2 (test case 2: Non-existing element).

  • Line 31: We assert that an error should occur when retrieving non-existent elements.

Conclusion

Unit testing is a fundamental practice for ensuring the correctness and reliability of your code. Following the conventions and techniques outlined in this Answer, we can write robust tests that help identify and fix issues early in the development process, ultimately leading to higher code quality and more maintainable software.

Unlock your potential: Golang series, all in one place!

To continue your exploration of Golang, check out our series of Answers below:

Free Resources

Copyright ©2025 Educative, Inc. All rights reserved