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
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
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
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
I'm using the variable names
in for the input we will pass to
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.
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
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
out variables with
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.