These days writing automated tests for your software is just as important as any other phase in the development life cycle.
In this article, I’ll go over how I write unit tests for HTTP servers in Go.
Install stretchr/testify
To make testing easier, I’ll be using the stretchr/testify, which gives access to many testing utils.
After initializing your go project, run the following command to install the testing library:
$ go get -u github.com/stretchr/testify
Testing http.HandlerFunc
An http.HandlerFunc is a function that will be called when a request hits its corresponding endpoint, and has the following signature:
type HandlerFunc func(ResponseWriter, *Request)
In this example, we’ll be working with a very simple http.HandlerFunc that returns some JSON.
Anatomy of the viewIndex function:
- Sets the Content-Type header to application/json.
- Writes a JSON body with one field “message” that maps to the string “Hello Gophers!” to the http.ResponseWriter.
By writing unit tests, we want to ensure that:
- The Content-Type header is set application/json.
- The status code is 200 since we didn’t explicitly modify it.
- The response body is a valid JSON with one field “message” which maps to the string “Hello Gophers!”.
The key points:
- Line 3: We create a new http.Request using the http.NewRequest function, which has the following signature:
func NewRequest(method, url string, body io.Reader) (*Request, error)
In our case, we won’t be sending any HTTP requests but rather mocking one. So, we pass an empty string as the url parameter, and nil as the body parameter, since we won’t be processing any data within our http.HandlerFunc.
- Line 6: We instantiate a new httptest.ResponseRecorder, which implements the Header, Write, and WriteHeader methods, making it an http.ResponseWriter. The httptest.ResponseRecorder can be used to record mutations on an http.ResponseWriter object.
- Line 7: Using the rec (http.ResponseWriter) and req (http.Request) variables, we can mock the whole HTTP Request/Response cycle. We pass those variables to the viewIndex (http.HandlerFunc). The response will be recorded to the rec (http.ResponseRecorder) variable.
And finally, we use the utility functions provided by the assert package to test the response recorded from the viewIndex (http.HandlerFunc).
Running the test:
To run this test function, run the following command:
$ go test -run=TestViewIndex
Just what we expected, our test passes.
Testing HTTP servers
The http.ServeMux can be used as an HTTP router that maps incoming requests to their handlers.
In our case, we’ll only be handling one endpoint “/home” using the viewIndex (http.HandlerFunc) that we defined in the first part of this article.
By writing unit tests, we want to ensure that:
- The viewIndex function will be triggered when incoming requests hit the “/home” endpoint.
- A 404 response should be issued to all other incoming requests.
The key points:
- Line 3–4: Instead of manually spinning up an HTTP server, we’ll be using the httptest.NewServer function which will start an HTTP server meant for testing in a separate goroutine and return a pointer to an httptest.Server instance, which we’ll use to stop the server once the testing is done.
- Line 6–11: In this part of the article, we’ll be using a table-driven testing approach. So we define a new struct, that will represent a test case. A test case will have a unique name, an endpoint that will be tested, and a shouldFail boolean field that will define the expected outcome.
- Line 17: For each test case, we run a subtest using the t.Run method.
- Line 19: Our testing server is now up and running, and ready to receive requests. The server URL can be accessed using the httptest.Server.URL property. To send a GET request, we make use of the http.Get function which has the following signature:
func Get(url string) (resp *Response, err error)
This function returns a pointer to an http.Response instance and an error. HTTP response-related information is now stored in the resp variable.
- Line 22: For test cases that are expected to return a 404 response, we test that the returned status code is in fact 404.
Running the test:
$ go test -run=TestIndex
Conclusion
In this article, we went through the implementation of unit tests for HTTP servers and handlers in Go.
We made use of the net/http/httptest and stretchr/testify packages to make the implementation seamless and easy.
All the source code of this project is hosted on GitHub