Simplify Golang test fixtures with this one weird trick
When testing code, test functions may depend on some external dependency. For example, if we have written a:
- Web crawler, we may want to test it on a real website
- Web server, we may want to test
GET
methods against a real database
Test fixtures are functions which launch the external dependencies our test code requires.
Say we're writing a web crawler and want to test it against a real website. We
can define startServer
and stopServer
functions which are called by our test
functions to serve the website required for our testing:
func startServer() net.Listener {
// Start server
return listener
}
func stopServer(l net.Listener) {
// Stop server
}
func TestFeature(t *testing.T) {
listener := startServer()
defer stopServer(listener)
// Run test
}
We can simplify this code by making the start function return the stop function:
func startServer() func {
// Start server
func stopServer() {
// Stop server
}
return stopServer
}
func TestFeature(t *testing.T) {
stopServer := startServer()
defer stopServer()
// Run test
}
This pattern has two benefits:
startServer
andstopServer
are inherently linked and should always be called together. Binding the two functions together makes this explicit.- In the first example, the writer of
TestFeature
must know thatstartServer
returns anet.Listener
, which must be passed tostopServer
. This leaks implementation detail. In the second example, this information is hidden1, giving a cleaner API. ThestartServer
andstopServer
functions can be changed internally, without affecting test code.
The nested functions in the second example form a closure, in which the
net.Listener
is persisted.