#Beginner's Guide to Logging in Tests in Golang
Welcome Gophers! In this tutorial, we’re going to be looking at how you can add log messages to your Go unit tests.
This is critical as your suite of tests around your applications grows over time. You want to be able to quickly identify why a test has failed so that you can quickly start digging into how to fix it.
Video Tutorial
Log and Logf
Let’s start with Log and Logf methods. These are helpful logging methods that hang off the *testing.T struct available to
us within our test functions:
t.Run("subtest 1", func(t *testing.T) {
t.Log("running subtest 1")
t.Logf("format string: %s", "hello world")
})
These helpful functions are subtly different from your standard fmt package print statements in the sense that they only log out if your test has failed, or if you are running your tests in verbose mode.
Error and Errorf
We’ve also got Error and Errorf available to us. These are effectively Log or Logf statements with the added functionality of calling t.Fail() as well. This will mean your particular test gets marked as failed.
t.Run("subtest 2", func(t *testing.T) {
t.Error("Some error running subtest 2")
// t.Fail() <- this gets called by t.Error!
})
Fatal and Fatalf
Finally, let’s look at Fatal and Fatalf - these are another step up from Error and Errorf as they call FailNow which means that your current tests stop immediately and no sub-tests are run:
t.Run("failing tests", func(t *testing.T) {
result := map[string]string{
"test": "results",
}
t.Fatalf("Some massive error has occurred: %+v", result)
t.Run("tests that wont run", func(t *testing.T) {
t.Log("an example of a test")
})
})
Let’s take a look at this now - as you can see from the output, the tests that won't run sub-test is never executed:
go test -v
=== RUN TestSomething
=== RUN TestSomething/subtest_1
main_test.go:7: running subtest 1
main_test.go:9: format string: hello world
=== RUN TestSomething/failing_tests
main_test.go:21: Some massive error has occurred: map[test:results]
--- FAIL: TestSomething (0.00s)
--- PASS: TestSomething/subtest_1 (0.00s)
--- FAIL: TestSomething/failing_tests (0.00s)
FAIL
exit status 1
FAIL example.com 0.196s
Helper Functions
You can also create helper functions that wrap logging to reduce boilerplate in your tests. These are particularly useful when you have common logging patterns across multiple tests:
func helper(t *testing.T, msg string) {
t.Helper()
t.Logf("HELPER: %s", msg)
}
func TestWithHelper(t *testing.T) {
helper(t, "This log message comes from a helper function")
}
The t.Helper() call tells the test runner that this function is a test helper, so error reports point to the caller’s location rather than the helper function itself.
Conclusion
Logging in tests is essential for understanding failures and debugging your code. The testing package provides several methods for adding meaningful output to your tests:
- Use
Log()andLogf()for general information that appears in verbose mode or on failure - Use
Error()andErrorf()to mark tests as failed while logging a message - Use
Fatal()andFatalf()to immediately stop test execution - Create helper functions with
t.Helper()to reduce duplication
With these tools at your disposal, you can write tests that are both clear and maintainable!
Further Reading
If you enjoyed this, you may like my other articles on testing in Go:
Continue Learning
Using T.Cleanup for Test Teardown in Go
In this tutorial, we are going to look at how you can use t.Cleanup() to properly manage test teardown and resource cleanup in your Go test suites.
An Introduction to Testing in Go
In this tutorial, we look at how to properly implement tests within your Go based systems using the go test tool
The Complete Guide to Testing in Go
Master Go testing with comprehensive coverage of unit tests, benchmarking, mocking, integration tests, and fuzzing in Go 1.26+
Supercharge Your Go Tests Using Fake HTTP Services
Learn how to write reliable tests in Go by using fake HTTP services. This tutorial covers examples with httptest and the fakes library.