Writing unit tests in Golang Part 2: Mocking
If you haven’t read Part 1 of this series, please don’t forget to check it out.
In software development, a program usually consists of many layers and functions which means multiple chunks of code have to be working together. Thus, it causes a lot of pain for programmers to create such a test to check that all parts are working properly. It will be a lot nicer to test each chunk of code individually. This is because it is easier to detect bugs or develop features when working with less code. And if all chunks of codes are working, the whole program is working.
We’ve learnt that unit testing is a kind of test that validates each smallest part of a program. You may ask … “ What if, my code (or my function) interacts with other modules? Something like databases, web servers, etc. Do I actually have to connect or transmit data to those modules when writing tests? ”
The answer is: no and I encourage you not to do so.
The next question is “ How to do it then? How do I test my code that interacts with other modules without actually doing it? ”.
The answer is: you can mock your code where it interacts with other modules. Mocking is a way of creating a stub (a fake, a not the real, you name it) piece of code that substitutes some functions of the whole code. Its purpose is to make testing easier because you can control those stub functions’ inputs and outputs freely which allows you to focus only on the business logic.
Example
The idea
I’m creating a computer with a printer as its component. The computer will have a function called PrintA4
which prints whatever content we provide in an A4 paper. The printer will also have its own function called Print
which will print whatever content and paper size we provide.
The code
The following code is a demonstration of an application that will be used as a mocking example. There’s a Computer
struct that has Pr
as its field. The Pr
field implements Printer
interface. The Printer
interface has a method Print
. Also, MyPrinter
is a struct that has a method Print
. You can see that MyPrinter
implements interface Printer
. The Computer
struct has a method PrintA4
.
Now, we want to test the function PrintA4
which calls Pr.Print
(Line12). The printer in this code is like “other module” we’ve discussed earlier. In order to test PrintA4
, we don’t actually have to print it from the printer. Therefore, we will create a stub function that mocks the function Print
of the Printer
interface. The test code is shown below.
Another main feature of testify is mocking. Testify offers you a mock
package that you could create your own mock object to test your code.
On Line 10~21, I have created a mock object. This object will mock the Printer
interface. On Line 30, you can see that the mock object calls the method On
. This method sets expectations of the given mocked function. As in the example, I’ve set an expectation that the function Print
will receive 2 arguments which are "A4"
and "hello world"
. After that, it calls Return
to tell the mock object to reutrn nil
as its return value.
On Line 37, when PrintA4
is called, it will call Pr.Print
. On production, it should call Print
of the object MyPrinter
but in this case, it will call Print
of the PrinterMock
object instead. This is so great because we don’t actually have to call the real function. We don’t have to interact with other modules but only our stub functions. This makes the code testable and easier to debug.
Finally, we assert our expectation with AssertExpectations
function. This function validates that the input of the stub function is correct and the stub function is properly called.
Generating Mocks using Mockery
One question comes to your mind “What if… what if my function has a lot of objects? And what if there’re many function calls of different objects inside my function, would I have to write all those mocks?”
The answer is: well yes but actually no.
Mockery is a very handy package that generates mocks for you. This way you won’t ever have to write mocks yourself because there’s an automation that handles such a job for you.
To use mockery, you can simply just type go get github.com/vektra/mockery/v2/.../
and then go rungithub.com/vektra/mockery/v2 --all
.
The mocks will be auto-generated for you into mocks
folder. The example generated code from the Printer
interface is shown below.
And that’s it for part two. In the next part, I will be talking about test suite which is the last topic we will discuss. And for now, stay tuned and happy coding!
References
How I mock unit tests in Golang by Christian Seki
Mocking Techniques for Go by Kyle Yost