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:
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.
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 functionfunc 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.
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 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) } } }
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 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) }
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.
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:
What is the NewReplacer function in golang?
Learn how Go's strings.NewReplacer()
efficiently replaces multiple substrings in a single pass, avoiding sequential replacements.
Type Assertions and Type Switches in Golang
Learn how type assertions and type switches in Go enable dynamic type handling within interfaces, ensuring type safety and flexibility for robust and efficient programming.
What is the fan-out/fan-in pattern in Golang
Learn how the fan-out/fan-in pattern in Go parallelizes tasks using goroutines and channels, enabling concurrent execution and efficient result aggregation.
Getting Started with Golang Unit Testing
Learn how to perform unit testing in Go by creating _test.go
files, using the testing
package, and writing clear test cases.
How to parse xml file to csv using golang with dynamic shcema?
Learn how to use Go's encoding/xml
and encoding/csv
packages to dynamically convert XML files to CSV.
Free Resources