Improving Your Go Tests and Mocks With Testify
Assertions are something that I genuinely feel the standard library in Go is missing. You can most definitely achieve the same results with the likes of if
comparisons and whatever else, but it’s not the cleanest way to write your test files.
This is where the likes of stretchr/testify comes in to save the day. This package has quickly become one of the most popular testing packages, if not the
Its elegant syntax allows you to write incredibly easy assertions that just make sense.
Getting Started
The first thing we’ll have to do in order to get up and running with the testify package is to install it. Now, if you are using Go Modules then this will just be a case of calling go test ...
*_test.go
files.
However, if you are still stuck on an older version of Go, you can get this package by typing:
|
|
After you have done this, we should be good to start incorporating it into our various testing suites.
A Simple Example
Let’s start off by looking at how we would traditionally write tests in Go. This should give us a good idea of what testify
brings to the table in terms of improved readability.
We’ll start by defining a really simple Go program that features one exported function, Calculate()
.
|
|
If we were to write tests for this using traditional methods, we would typically end up with something like this:
|
|
We can then try run this simple test by calling go test ./... -v
, passing in the -v
flag to ensure we can see a more verbose output.
If we wanted to be a bit fancier, we might incorporate table-driven tests in here to ensure a wide variety of cases were tested. For now though, let’s try and modify this basic approach to see how testify
works:
|
|
Awesome, as you can see, we’ve managed to succinctly test for equality using the assert.Equal
function. Straight away this looks like an improvement as we’ve got fewer lines of code to read over and we can clearly see what the test function is trying to achieve.
Negative Test Cases and Nil Tests
So, we’ve looked at happy path testing, but how about negative assertions and Nil checks. Well, thankfully the testify
package has methods that allow us to test for both.
Say we wanted to test a function that returns a status of a given application. For example, if the application was alive and waiting for requests then the status would return "waiting"
, if it had crashed, then it would return "down"
as well as a variety of other statuses for when it’s serving a request, or when it’s waiting on a third party, etc.
When we perform our test, we would want our test to pass as long as the status equaled anything but "down"
, so we could use assert.NotEqual()
in this particular, hypothetical case.
|
|
If we wanted to test to see if "status"
was not nil then we could use either assert.Nil(status)
or assert.NotNil(object)
depending on how we wish to react to it being nil
.
Combining Testify with Table-Driven Tests
Incorporating testify
into our test suites doesn’t necessarily preclude us from using methods such as table-driven testing, in fact, it makes it simpler.
|
|
Notice the slight difference between how we called assert.Equal()
in this example compared to the previous example. We’ve initialized assert using assert.New(t)
and we are now able to call assert.Equal()
multiple times, just passing in the input and the expected values as opposed to having to pass t
in as our first parameter every time. This isn’t a big deal, but it certainly helps to make our tests look cleaner.
Mocking
Another excellent feature of the testify
package is it’s mocking capabilities. Mocking effectively allows us to write replacement objects that mock the behaviors of certain objects in our code that we don’t necessarily want to trigger every time we run our test suite.
This could be, for example, a messaging service or an email service that fires off emails to clients whenever it’s called. If we are actively developing our codebase, we might be running our tests hundreds of times per day, and we might not want to send out hundreds of emails and/or messages a day to clients as they may start to take umbrage.
So, how do we go about mocking using the testify
package?
A Mocking Example
Let’s take a look at how we can put mocks
to use with a fairly simple example. In this example, we’ve got a system that will attempt to charge a customer for a product or service. When this ChargeCustomer()
method is called, it will subsequently call a Message Service which will send off an SMS text message to the customer to inform them the amount they have been charged.
|
|
So, how do we go about testing this to ensure we don’t drive our customers crazy? Well, we mock out our SMSService by creating a new struct
called smsServiceMock
and add mock.Mock to its list of fields.
We then stub out our SendChargeNotification
method so that it doesn’t actually send a notification to our clients and return a nil
error.
Finally, we create our TestChargeCustomer
test function which in turn instantiates a new instance of type smsServiceMock
and specifies what should happen when SendChargeNotification
is called.
|
|
So, when we run this calling go test ./... -v
we should see the following output:
go test ./... -v
=== RUN TestChargeCustomer
Mocked charge notification function
Value passed in: 100
Charging Customer For the value of 100
--- PASS: TestChargeCustomer (0.00s)
main_test.go:33: PASS: SendChargeNotification(int)
PASS
ok _/Users/elliot/Documents/Projects/tutorials/golang/go-testify-tutorial 0.012s
As you can see, our mocked method was called as opposed to our “production” method and we’ve been able to verify that our myService.ChargeCustomer()
method acts the way we expect it to!
Happy days, we’ve now been able to fully test a more complex project using mocks. It’s worth noting that this technique can be used for all manner of different systems, such as mocking database queries or how you interact with other APIs. Overall, mocking is something that is really powerful and is definitely something you should try to master if you are going to be testing production-grade systems in Go.
Generating Mocks with Mockery
So, in the above example we mocked out all of the various methods ourselves, but in real-life examples, this may represent a hell of a lot of different methods and functions to mock.
Thankfully, this is where the vektra/mockery package comes to our aide.
The mockery binary can take in the name of any interfaces
you may have defined within your Go packages and it’ll automatically output the generated mocks to mocks/InterfaceName.go
. This is seriously handy when you want to save yourself a tonne of time and it’s a tool I would highly recommend checking out!
Key Takeaways
- Testify helps you to simplify the way you write assertions within your test cases.
- Testify can also be used to mock objects within your testing framework to ensure you aren’t calling production endpoints whenever you test.
Conclusion
Hopefully, this has helped to demystify the art of testing your Go projects using the stretchr/testify
package. In this tutorial, we’ve managed to look at how you can use assertions from the testify
package to do things like assert if things are equal, or not equal or nil.
We’ve also been able to look at how you can mock out various parts of your systems to ensure that, when running your tests, you don’t subsequently start interacting with production systems and doing things you didn’t quite want to.
If you found this useful, or if you have any comments or feedback, then please feel free to let me know in the comments section below.
Further Reading
If you enjoyed this, you may like my other articles on testing in Go: