A unit test for the JSON function

Let's write some unit tests as a way of getting familiar with Go's built-in testing library. Looking in server.go, we can find the first function that isn't main and write a test for it. So let us write a test for the JSON function, starting with making a failing test and running it. We start by creating a server_test.go file in the same directory as server.go. Go will automatically know this is a test file because of the ending _test.go, and naming it starting with server lets us know that we'll be writing tests for server.go in it.

In the same directory as the test file we run our new test with go test and we will see our expected failure from the t.Fatal:

Great! Now in order to replace that with something which will actually test the function, let's think about what the function is doing. It encodes arbitrary data and then writes it to a http.ResponseWriter. So if we want to test it, we need to

  • supply the data

  • know what its encoded form looks like

  • check that it was written to the http.ResponseWriter

Because we are currently only using JSON to encode data of the type map[string]string, that seems like a good type to test. So - let's test the message "hello world" in a map form. Add these lines at the top of the TestJSON function:

I'm using the variable names in for the input we will pass to JSON, and out for the output we expect to see from it.

This is great and should be a good test, but we have an issue: JSON takes an input but it doesn't return an output. So we write to the response writer, but have no way of checking that data. Luckily, Go's standard library has something that will help us: the httptest library - and it is lovely!

httptest.ResponseRecorder is a struct that implements the http.ResponseWriter interface but also allows you to check what changes were made to it afterwards. If we use one of these, we can pass it with our message into JSON and then check the results after.

Use it and call JSON with it like this:

JSON will now have written to our ResponseRecorder, giving us the ability to check those values. For now let us only test that the body written to the recorder is the same as our input message. We can access it with its Result() method which gives us the http.Response we normally get when making a request.

From that we can read the entire body, convert it to a string, and compare it with our input.

Note that we use t.Fatalf here if we fail to read the body to indicate that our test has failed to run and we shouldn't continue. We always expect to be able to read it here, so if we can not, something is seriously wrong.

We use t.Errorf for our actual test case to show that it has failed, but the other tests should continue.

Putting it all together looks like this:

Enhancing the JSON unit test with tables#

Our test is currently fairly simple. It only checks the body of the response, not any headers, and it only tests one message and one data type. Luckily, Go has a common pattern we can use to scale this up: table-driven tests!

Instead of having only one message to test, we can make a slice of them, with both our input and expected output. We can then use a for loop to run all of our tests one after the other. Adding a new case will then be as simple as adding another element to the slice - we'll do that shortly by adding the {"hello":"tables"} message.

Before we do that, we need to convert our first test case into a table of test cases, including both our input and our expected output. Our input is a map of string to string, and our output is a string. So we need to create a slice of these inputs and outputs. Fortunately, we can use an anonymous struct, which is a struct that is defined without a name. It will let us create our desired slice and immediately create values to fill it. Also, because it is defined only in this one test case, future test cases can use their own anonymous structs if they have different inputs or outputs. The syntax looks slightly weird, because we are used to having our struct definition separate from instantiation. Combined with the fact that this is also a struct means there are a whole lot of curly braces. But it gives us exactly what we're looking for, which is great.

In this section of code we are not defining a type for our struct but defining the struct inline. It's not necessarily the best practice, but so long as we are only using this structure here it should be fine. If we find ourselves wanting to use this in more than one test case we can convert it to a regular struct and reuse it.

With this struct, we can replace our in and out variables with test.in and test.out, which is our range variable over our testCases slice. We also create a loop around the main section of our test to loop through it with the one case we have provided. The loop encompasses everything from creating the recorder to the last if statement checking our output.

Now that we've converted our first test into a table-driven test, let us make sure it still works! go test will do the job.

 

This page is a preview of Reliable Webservers with Go

Start a new discussion. All notification go to the author.