Testing in Go: Mocking MVC using Testify and Mockery

Galangkangin Gotera
10 min readMay 1, 2021
not this kind of mocking

One of the things that scare away people from doing TDD, especially in MVC (model-view-controller) architecture, is writing tests for the upper layers.

Case 1#: Let’s say you have the usual layer of controller-service-repository where controller depends on service, and service depends on the repository. If you’re writing tests without any techniques, then when writing tests for a controller, you need to make sure that your service implementation is correct, which means your repository implementation must also be correct. Meanwhile, your test is just a simple error handling test :( When the test succeeds, there are two possibilities: the error is handled correctly, or the service layer has a bug and did not return any error. “But, my service is correct!” yeah, that's what they all said. It would be best if you had a way to make sure that it's always former and never the latter.

Case 2#: Let's say you have a service ArticleServicewhich needs to get and combine data from multiple repositories, such as ArticleRepository , UsersRepository , and TagsRepositories (yeah, it's MongoDB). However, the implementation of TagsRepositories is not completed yet and is delegated to your teammate. How would you code your service without one of its dependencies?

Mocking

Mocking is the act of creating pre-programmed objects with expectations which form a specification of the calls they are expected to receive

Martin Fowler

With mocking, we can create an instance of the dependent service, which we can preprogram to behave a certain way on a certain input.

Let's take the TagsRepository case from the previous example. Now we are testing GetByID(id) Which returns the full details of an article. The flow is as follows:

  • Get the normalized article data from ArticleRepository
  • Get the writer’s information of this article from UserRepository . Fetched by the UserID stored on the article.
  • Get the tag’s information of this article from TagRepository . Fetched by the TagID ‘s stored on the article.

Now want to test if our service logic can successfully merge the data returned from TagRepository. We create the following test suite in Go:

func TestArticleServiceShouldUseTag(t *testing.T) {
as := new(ArticleService)
as.ArticleRepository = // get article repository
as.UserRepository = // get user repository
as.TagRepository = MockTagRepository

res, err := as.GetByID(10)
// using testify assertion
assert.Equal(t, "tag9", res.tags[0].name)
assert.Equal(t, "tag6", res.tags[1].name)
}

Assume for the sake of explanation that we use the real implementation of ArticleRepository and UserRepository, and prepared an article in our database with tagsID = [9, 6] . Now, we are interested in our service logic can use the data returned from MockTagsRepository correctly.

Here's the fun part. We know that in our test suite, only TagsRepository.GetByID(9) and TagsRepository.GetByID(6) is going to get called. We also assume that the repository will be tested well without bugs on the real (unfinished) implementation. So, we can engineer MockTagRepository so that it always returns {"name": "tag9"} when called with GetByID(9) and {"name": "tag6"} when called with GetByID(6) . Therefore, the test will pass if we implemented the service correctly, without waiting for the actual TagsRepository to finish.

That is the beauty of mocking. You can develop components in a large network of dependencies independently without having the real implementation of the dependencies ready. What if they are ready? It’s still better to test using mock objects so that the test is isolated and you can be 100% sure that it's the current tested code’s fault and not the dependencies fault. Also, you don't want to accidentally create hundreds of database entries for each unit test on the controller layer, right :). This is known as isolation testing, which breaks down the application into modules and testing them without calling other modules. In this case, we test each layer (repository, service, controller) separately without calling code from a different layer.

Mocking Vs. Stubbing

Another common term is stubbing, which is another technique to isolate testing. According to Martin Fowler, in stubbing, you create a “simulated version” of a class. In contrast, you specify what output it should give without any other internal logic for a certain input in mocking.

Let's make a stub example of the previous case of TagRepository. We call the stub object StubTagRepository . Now we must actually define the method GetByID . But here, we ‘simulate’ getting and for each get actually returns a straightforward object like so:

func (r StubTagRepository) GetByID(id int) (res tag) {

res.name = "tag" + strconv.Itoa(id)
return
}

Notice that when calling GetByID(9) and GetByID(6) , both MockTagRepository and StubTagRepository will both return tags with id tag9 and tag6 respectively. However, callingGetByID(5) on StubTagRepostory will return a tag with name tag5 while it will crash on MockTagRepository because we did not specify what to output for that input.

I hope this clears out the difference between stub and mocks. On stubbing, you create an object that still has implementation (albeit simulation) inside, whereas, on mocking, you only specify for each specific input what output the object should give.

In this article and for my projects, I will only use mocks, so only that will be discussed here. But it’s still nice to know the difference between mocks and stubs. Fortunately, Go has testify and mockery , an amazing mocking library that provides us with an easy way to create mock objects.

MVC Project Structure

Before we go any further, it’s best to briefly show how my Go MVC project is structured. I am not using any go frameworks like Beego because my team decided Go’s native way is simple. The folders on our project are as following:

controllers/
services/
repositories/
models/

This looks like the standard back-end MVC structure, where:

  • controllers are where we receive the incoming requests. From here, we call services to get what the request wants.
  • services are where the largest amount of business logic is located. Here we get data from one or more repositories and combining them, validate fields for insert and update requests, etc.
  • repositories are connectors to a single database table. This provides a simplified interface for communicating to the database.
  • models are where our gorm database tables are defined.

I hope you have a basic understanding of MVC because I won’t explain further since it’s not the focus today. However, I will explain how we set up our objects, so it’s easy to do mocking and other unit tests.

example code from one of our service (SuratTuntutanService

We define an interface for each service and repository so that we can later create mock implementations on classes that depend on it. We create a concrete struct that implements the interface on the file intended as the actual object. We then create an initializer for the concrete struct for easier creation. Take the above example; the initializer InitSuratTuntutanService takes an interface of ISuratTuntutanRepository because it only depends on it. Therefore it only depends on an interface and not the concrete object directly. By structuring it like this, our code only will still compile, although there is no concrete SuratTuntutanRepository yet. This will become very important later on.

Now that you know how my project is structured, it’s time to get the testing part. Do note that you do not have to structure it like this, and you can even use go frameworks. Hopefully, after I explain mocking, you can adapt them to your project structure. However, the main takeaway is that your structs should depend on interfaces and not concrete structs. That is what makes it possible to do mocking.

Testing with mocking using Testify

I explained mocking and my project structure; now it’s time to create some mocking tests. Let’s take the previous SuratTuntutanService as an example. I want to develop List method next. This is a simple listing of the database with pagination and filtering functionalities stored on queryParams . I always develop code using TDD, but for the sake of explanation, let’s say I created the whole implementation first before creating the tests:

First, let’s think of the possible cases that may occur:

  • Called the method with a large enough limit
  • Called the method with negative page
  • The repository returns an error
  • The repository does not return an error and returns valid data

Question 1: why do we not consider the case when the repository does not return an error but returns invalid data? We will answer this later.

Now let's create the tests. The first two cases are easy to test since they do not need to call the repository struct. But what about the last two cases? should we first implement the actual suratTuntutanRepository ? No, we do not need to use the same class on testing for the actual code. Here we are only interested in testing SuratTuntutanService We can assume our actual implementation of the repository will be correct and create a mock repository class to test this service.

The last sentence answers Question 1. We do not create that scenario because if it happened, it's a bug on the repository and should be spotted when testing the repository.

Testify provides a mock library to generate mocks easily. All you gotta do is define the mock class and add mock.Mock , then code the “implementations” since go still requires them, although its a mock class:

that method O_O. Don't worry, It’s just boilerplate testify code. Basically, mock.Mock has a method built-inCalled which checks if we have preprogrammed the response for the called page , limit , and queryParam and will return the preprogrammed response as a special object. The values of this object can be obtained using Get(i), which will give the i’th returned value. Get returns the general interface{} object, so you need to case it to the actual returned type. There are shorthand methods you can use without these castings, such as:

  • .Int(i), get the i’th returned value and cast it to integer.
  • .Error(i), get the i’th returned value and cast it to error.
  • And similarly for other common types.

What are preprogrammed responses? Recall the TagsRepository the case at the beginning of the article. We preprogram the mock class so that it always return {name: "tag9"} when calling GetByID(9) . To preprogram a response on a testify mock object, you use the .On(methodName, ...params).Return(..values) . Take a look at the example

testing if our repo gives no error and valid list, we should return that same list

Line-by-line explanation:

  • Line 2: we get a static list that will be used for testing purposes
  • Line 4: creates an instance of the mock repository
  • Line 5: preprograms the mock repository so whenList(4, 2, {})is called it will return (stl, nil) . This is an important line.
  • Line 6: Creates an instance of the service (the object we want to test). Here, we pass our mock object as the repository. Now when our service calls the repository, our mock repository’s method will be called.
  • Line 10: the function we want to test
  • Line 11–12: verifying that the function is correct.

What if you want to make sure that the repository is not called if an error occurred on the service? An example forList method is when we call it with a negative page value. Testify mocks provide an assertion object AssertNumberOfCalls(t, methodName, numberOfCalls) which verifies the number of times the method is called. Let's check the test for the negative page below:

The last line checks if we called List method exactly zero times. These tests are useful to check whether our layers correctly catch our errors without propagating them to the lower layers.

Test with mocking using Mockery

wow mockery is such a sick name!

In my opinion, mocking with testify is very clean. And indeed, for a week, I created tests purely using testify. Then, my friend introduced me to mockery.

Remember when we’re preparing our testify mock class? While not hard, it’s pretty standard and boilerplate. When using the mock classes on our test, we only care about the preprogramming functionalities (i.e.: On , Return , AssertNumberOfCalls ) so we do not care how we implement the mock methods. Mockery is a command line tool that auto-generates ready-to-use testify mock classes for all interfaces on your program.

By simply defining the original interface and its method, i.e.:

and running

mockery --all --keeptree

It generates mocks/services/ISuratTuntutanRepository.go with the following contents:

Amazing, it generates all the boilerplate codes so you can directly import mocks.ISuratTuntutanRepository to your testing code. So, the previous test would look something like this:

notice that we are importing our mock class from package mocks and not redefining it here.

Another huge benefit of using mockery other than convenience is standardization. In my current project, all of the back-end guys use mockery to generate their mock class. Therefore, there are never any merge conflicts when two people work on the same feature because mockery generates the same code for the same interface. This gives us more time to code the actual thing rather than adjusting the indentation and ordering of the boilerplate mock functions when using testify as-is.

How Mocking Solves our Cases?

Now that we had explored what mocking is and what it does, let's look at our original case and how mocking solves them.

Case 1#

Recall when we’re testing ifSuratTuntutanService.List returns the same list given by SuratTuntutanRepository . Notice that we preprogram the output of SuratTuntutanRepository.List to always return a valid list with no error. Therefore, if an error occurred on this test, the bug must definitely be the service since the repository guarantees valid data. This suggests that you should always test using mocks. However, the actual implementation is ready because it assures you that the problem is on the currently tested service and not the dependencies.

Case 2#

Same case as the first one. Note that the whole process from implementing the List method on service to testing the service, we do not need to create the actual implementation of SuratTuntutanRepository . This solves the case because you can continue to develop your services and tests without waiting for the actual repository to be finished. This is especially useful if you want to go fast, but your friends have other things to do and didn't finish your dependencies.

Conclusion

Mocking is a pivotal testing technique. It allows each developer to work independently without waiting on each other’s work. It also creates isolated tests so that if it fails, you know that it’s definitely the currently tested component, increasing the developers' confidence. Finally, from my experience, mocking forces the developers to think about every possible case given by the actual repository and create tests for each of them, therefore, giving higher overall test quality and, in turn, fewer bugs.

References

--

--