Responses (0)
Hey there! 👋 Want to get 5 free lessons for our Reliable Webservers with Go course?
Testing plays a fundamental role in the development of quality software. Shipping and deploying software with undetected bugs and regressions opens up a can of terrible consequences such as losing the trust of end users or costing the business time and resources. In a large collaborative setting, having developers manually test each and every feature and user flow for bugs and regressions wastes valuable time that can be put towards improving other aspects of the software. As the codebase and team grows, this approach will not scale. By writing unit/integration/end-to-end tests, identifying and catching bugs and regressions throughout an entire codebase becomes a painless, automatable task that can easily be integrated into any continuous integration pipeline.
Unlike most other languages, the Go programming language provides a built-in, standard library package for testing: testing
. The testing
package offers many utilities for automating the testing of Go source files. To write a test in Go, define a function with a name prefixed with Test
(followed by a capitalized segment of text) and accepts an argument of struct type T
, which contains methods for failing and skipping tests, running multiple tests in parallel, formatting test logs, etc.
Example:
This test checks whether the Sum
function correctly calculates the sum of two integer numbers. If the sum does not match the expected value, then the test logs an error message and marks itself as having failed.
Try it out in the Go Playground here.
Below, I'm going to show you:
How to write a test for a route handler of a Go and
chi
RESTful API.How to mock a function called within a route handler.
Installation and Setup#
Clone a copy of the Go and chi
RESTful API from GitHub to your machine:
This RESTful API specifies five endpoints for performing operations on posts:
GET /posts
- Retrieve a list of posts.POST /posts
- Creates a post.GET /posts/{id}
- Retrieve a single post identified by itsid
.PUT /posts/{id}
- Update a single post identified by itsid
.DELETE /posts/{id}
- Delete a single post identified by itsid
.
If you would like to learn how to build this RESTful API, then please visit this blog post. In the testless
branch's version of the RESTful API, the posts.go
file has been moved to a routes
subdirectory, and the route handlers within this file have been refactored into a routes
package to allow the testing of packages independent of the main
package.
Run the following command to install the project's dependencies:
Note: If you run into installation issues, then verify that the version of Go running on your machine is v1.16.
Writing a Unit Test for a Route Handler#
To get started, let's create a posts_test.go
file within the routes
subdirectory. This file will contain tests for each of the route handlers within the posts.go
file.
Inside of posts.go
, each endpoint is defined on the chi
router via a corresponding routing method named as an HTTP method. To register the GET /posts
endpoint on this router, call the .Get
method with the route /
(in main.go
, this posts
sub-router is attached to the main router along the /posts
route).
(routes/posts.go
)
rs.List
(rs
refers to the PostsResource{}
struct) is a route handler method defined on the PostsResource{}
struct. It retrieves a list of posts from the JSONPlaceholder API:
(routes/posts.go
)
Let's write a unit test, which tests a single unit of code (commonly a function), for the PostsResource{}.List
route handler function. Start off with a simple failing test that will immediately fail and print the message "Not yet implemented." to the terminal:
(routes/posts_test.go
)
To test all packages within the current working directory recursively...
-v
- Log verbose messages about the tests, such as the amount of time a test took to pass/fail. Prints allLog
/Logf
calls (automatically called bytesting
methodsFatal
,Fatalf
, etc.)../..
- Recursively tests all packages within the current working directory. Omitting this raises the error "no test files" and run none of the sub-package tests since by default, Go only tests the root package (main
).

To test the route handler for GET /posts
:
Create a new
GET
request to send to/posts
via thehttp
package'sNewRequest
method. This request will be passed to the route handler.
The original route handler
PostsResource{}.List
provides aResponseWriter
, which constructs an HTTP response, and aRequest
, which represents an incoming HTTP request. For tests to observe and check the changes made to an HTTP response (e.g., the contents of the response body, the headers set to the response and the status code of the response), use thehttptest
package'sNewRecorder
method, which records theResponseWriter
's mutations. TheResponseRecorder
is an implementation ofResponseWriter
, which means any method that acceptsResponseWriter
as an argument can also acceptResponseRecorder
.
In the context of this test, the
PostsResource{}.List
method is an ordinary function. To have it treated as an HTTP route handler, pass it to thehttp
package'sHandlerFunc
method. SincePostsResource{}.List
already has the appropriate function signature ofResponseWriter
andRequest
, it will be called anytime its handler representation is called.
Call the handler representation's
ServeHTTP
method to call the handler itself with the response recorder (rr
) and created request (req
). All of the response's data is written directly to this recorder, which substitutes theResponseWriter
. In this case, the list of posts retrieved from the JSONPlaceholder API (resp.Body
inPostsResource{}.List
) is copied to this recorder.
If any errors were encountered while retrieving the list of posts (i.e., the JSONPlaceholder API experiencing server downtime), then the response will return a non-
200
status code. When this happens, fail the test and log the error message.
Decode the body of the response and store the result within a variable. If any errors are encountered while decoding the body (i.e., improperly formatted data), then fail the test and log the error message.
The JSONPlaceholder API returns one hundred posts for the endpoint
GET https://jsonplaceholder.typicode.com/posts
. Therefore, we should expect the total items in the response body to be one hundred. Compare this expected total with the actual total from the response body. If the two values match, then pass the test. Otherwise, fail the test and log the error message.
Let's combine these code snippets together:
(routes/posts_test.go
)
Run the test:

Congrats! The route handler test passes!
Mocking a Function Called Within a Route Handler#
Notice how the TestGetPostsHandler
takes approximately half a second to run due to the network request the PostsResource{}.List
route handler sent to the JSONPlaceholder API. If the JSONPlaceholder API experiences heavy traffic or any server outages/downtime, then the network request may either take too long to send back a response or completely time out. Because our tests rely on the status of a third-party service, which we have no control over, our tests take much longer to finish running. Let's work on eliminating this unreliability factor from TestGetPostsHandler
.
PostsResource{}.List
calls the GetPosts
function, which sends this network request and pipes the response back into PostsResource{}.List
if there was no network request error encountered.
(routes/posts.go
)
Since the function responsible for sending the network request (GetPosts
) is a package-scoped variable within the routes
package, this function can be replaced with a mock function, which replaces the actual implementation of a function with one that simulates its behavior. Particularly, the network request will be simulated. As long as the mock function has the same function signature as the original function, calling the route handler in a test will remain the same.
Inside of posts_test.go
, add a mock function for GetPosts
:
(posts_test.go
)
This mock function creates some dummy data (mockedPosts
, which is a list containing one post), encodes this data as JSON via json.Marshal
and returns a minimal HTTP response with a status code and body. These fields adhere to the http
package's Response
struct.
At the top of the TestGetPostsHandler
test, set the GetPosts
package-scoped variable to this mock function and change the expectedTotal
to 1
:
Run the test:

Wow! The mock allows our test to run much faster.
Next Steps#
Click here for a final version of the route handler unit test.
Try writing tests for the other route handlers.