Writing Tests in Golang. (A Complete Guide)
Introduction
Writing tests is one aspect of development that is as important as writing the core code. This step is important as it ensures that the code works as expected. Tests can be of two types: unit tests and integration tests. Unit tests are tests that test a single function or method. Integration tests are tests that test the interaction between multiple functions or methods. In this article, we’ll explore how to write tests in Golang.
How to write tests in Golang
Golang has an inbuilt testing package that allows us to write tests. This package provides a framework for writing tests and running them. It also, provides a set of tools for writing tests.
To write a test in Golang, we need to create a file with the suffix _test.go based on the file we are testing. This file will contain the tests for the code in the file that it is testing. The test file will contain a function that starts with the word Test followed by the name of the function that it is testing. The function will take a pointer to a testing.T type as its only argument. The testing.T type provides a set of methods that we can use to write our tests.
package main
import "fmt"
func main() {
fmt.Println("Hello World")
}package main
import "testing"
func TestMain(t *testing.T) {
// Write your tests here.
}To run our tests, we can use the go test command. This command will run all the tests in the current directory. We can also use the -v flag to get more information about the tests that are being run.
go test -vTable Driven Tests
Table-driven tests are a way to write tests in Golang using a table-like format. This allows us to write tests more concisely. We can use a table to store the input and expected output for our tests. We can then loop through the table and run our tests. The tale is a slice of structs.
package main
import "testing"
func TestMain(t *testing.T) {
tests := []struct{
name string
input string
expected string
}{
{
name: "Test 1",
input: "Hello World",
expected: "Hello World",
},
{
name: "Test 2",
input: "Hello World",
expected: "Hello World",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// Write your tests here.
})
}
}How to interpret test results
When we run our tests, we will get a summary of the tests that we run. The summary will tell us how many tests were run, how many tests passed, and how many tests failed. The summary will also tell us how long it took to run the tests.
=== RUN TestMain
--- PASS: TestMain (0.00s)
PASS
ok Testly 0.010sMocking
Mocking is a way to test our code in isolation. We can mock a function by creating a function with the same signature as the function that we want to mock. We can then use this function to test our code. We can also use the testing package to mock functions. The testing package provides a Mock type that we can use to mock functions. The Mock type provides a Call method that we can use to mock a function. The Call method takes a function as its only argument. The function that we pass to the Call method will be called when the function that we are mocking is called.
package main
import "fmt"
func main() {
fmt.Println("Hello World")
}package main
import (
"testing"
"github.com/stretchr/testify/mock"
)
type MockedFunction struct {
mock.Mock
}
func (m *MockedFunction) MockedFunction() {
m.Called()
}
func TestMain(t *testing.T) {
mockedFunction := new(MockedFunction)
mockedFunction.On("MockedFunction").Return()
mockedFunction.MockedFunction()
mockedFunction.AssertExpectations(t)
}In the above example, we are mocking the MockedFunction function. We are then calling the MockedFunction function and asserting that it was called. We can also use the On method to set the return value of the function that we are mocking.
Testing HTTP
To test HTTP requests in Golang, we can use the httptest package. The httptest package provides a NewRecorder function that we can use to create a ResponseRecorder type. The ResponseRecorder type implements the http.ResponseWriter interface. We can use the ResponseRecorder type to test our HTTP handlers. We can use the ResponseRecorder type to get the response from our HTTP handler.
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World")
})
http.ListenAndServe(":8080", nil)
}package main
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestMain(t *testing.T) {
req, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World")
})
handler.ServeHTTP(rr, req)
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
}
expected := "Hello World"
if rr.Body.String() != expected {
t.Errorf("handler returned unexpected body: got %v want %v", rr.Body.String(), expected)
}
}The output of the above test will be:
=== RUN TestMain
--- PASS: TestMain (0.00s)
PASS
ok Testly 0.010sIn the main.go file, we are creating an HTTP server that listens on port 8080. We are creating an HTTP handler that returns the string Hello World when it receives a request. We can then test the handlers.
Testing concurrency
To test concurrency in Golang, we can use the sync package. The sync package provides a WaitGroup type that we can use to wait for a group of goroutines to finish. We can use the Add method to add a goroutine to the WaitGroup type. We can then use the Wait method to wait for all the goroutines to finish. We can also use the Done method to signal that a goroutine has finished.
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("Hello World")
time.Sleep(5 * time.Second)
}package main
import (
"sync"
"testing"
"time"
)
func TestMain(t *testing.T) {
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
time.Sleep(5 * time.Second)
}()
wg.Wait()
}In the above example, we are creating a WaitGroup type and adding a goroutine to it. We are then waiting for the goroutine to finish. We can also use the Done method to signal that a goroutine has finished.
Testing Time
To test time in Golang, we can use the time package. The time package provides a Now function that we can use to get the current time. We can use the Add method to add a duration to the current time. We can then use the Equal method to compare the current time with the time that we want to test.
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("Hello World")
}package main
import (
"testing"
"time"
)
func TestMain(t *testing.T) {
now := time.Now()
later := now.Add(5 * time.Second)
if !later.Equal(now) {
t.Errorf("time is not equal")
}
}In the above example, we are getting the current time and adding 5 seconds to it. We are then comparing the current time with the time that we want to test. If the times are equal, the test will pass. If the times are not equal, the test will fail.
Testing Errors
To test errors in Golang, the tesing package provides an Errorf function that we can use to fail a test. We can use the Errorf function to print an error message and fail a test.
package main
import (
"fmt"
)
func failingFunction() error {
return fmt.Errorf("error")
}
func main() {
fmt.Println("Hello World")
}package main
import (
"testing"
)
func TestFailingFunction(t *testing.T) {
err := failingFunction()
if err != nil {
t.Errorf("failingFunction returned an error: %v", err)
}
}In the above example, we are creating a function that returns an error. We are then calling the function and checking if the function returned an error. If the function returned an error, we are failing the test.
Testing Panics
Panics are a way to handle errors in Golang by crashing the program. To test panics in Golang, we can use the recover function. The recover function can be used to recover from a panic. We can use this to test if a function panics.
package main
import (
"fmt"
)
func funcPanics() {
panic("panic")
}
func doesNotPanic() {
fmt.Println("does not panic")
}
func main() {
fmt.Println("Hello World")
}package main
import (
"testing"
)
func TestFuncPanics(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Errorf("funcPanics did not panic")
}
}()
funcPanics()
}
func TestDoesNotPanic(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Errorf("doesNotPanic panicked")
}
}()
doesNotPanic()
}In the above example, we created two functions one that panics and one that does not panic. We are then using the recover function to test if the function panics. The first test will pass and the second test will fail.
=== RUN TestFuncPanics
--- PASS: TestFuncPanics (0.00s)
=== RUN TestDoesNotPanic
Hello World
main_test.go:20: doesNotPanic panicked
--- FAIL: TestDoesNotPanic (0.00s)
FAIL
exit status 1
FAIL Testly 0.007sTesting Logging
To test logging in Golang, we use Logf and Log functions from the testing package. We can use the Logf function to print a formatted message. We can use the Log function to print a message.
package main
import (
"fmt"
"log"
)
func funcLogs() {
log.Println("log")
}
func funcLogsF() {
log.Printf("log %v", "formatted")
}
func main() {
fmt.Println("Hello World")
}package main
import (
"testing"
)
func TestFuncLogs(t *testing.T) {
t.Logf("funcLogs: %v", "log")
}
func TestFuncLogsF(t *testing.T) {
t.Logf("funcLogsF: %v", "log formatted")
}Test Benchmarks
To test benchmarks in Golang, we can use the testing package. The testing package provides a Benchmark function that we can use to run benchmarks. We can use the Benchmark function to run benchmarks for a function.
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello World")
}package main
import (
"testing"
)
func BenchmarkMain(b *testing.B) {
for i := 0; i < b.N; i++ {
main()
}
}Testing benchmarks are covered in more detail in the Golang Benchmarking article.
Conclusion
In this article, we explored techniques for testing Golang code. We discussed how to test functions, variables, constants, time, errors, panics, logging, and benchmarks. Additionally, we discussed the use of the testing package to help facilitate the testing of the Golang code.

