Mocks in Go tests with Testify Mock
Tags: go, testing, webdev, codenewbie
Published: 2022-10-24 14:07:47

---
title: Mocks in Go tests with Testify Mock
published: true
description:
series: Intro to automated testing in Go
tags: #go, #testing, #webdev, #codenewbie
---

In parts 1-3 of this tutorial series, we saw how you can use Go to write automated tests. Go's testing is very conducive to giving test coverage to a lot of your codebase's functionality. However, there's one area in particular where it's harder to do automated tests: code that is **nondeterministic**.

When I say nondeterministic code, I am talking about code where **you don't have total control** over the code's logic and output. This can be things like:

* 🧮 Code that uses pseudorandom number generators, like `math/rand`
* 💻 Web API calls and their response payloads, or errors
* ⏰ The current time returned by `time.Now`

Luckily, one tool that helps with testing nondeterministic code, is the `mock` package of the popular [Testify testing framework](github.com/stretchr/testify), which you can use for mocking out these API calls.

For this tutorial, we'll look at how to mock out code that uses `math/rand`, then in a follow-up post, we'll mock out a fake web API client to see how to use Testify Mock in more complex scenarios.

Prerequisites for this tutorial are familiarity with the basics of writing and running a Go test, which the [first tutorial](https://dev.to/salesforceeng/intro-to-automated-testing-in-go-4mjl) in this series covers.

## 🎰 Getting some nondeterministic code and the Testify Mock package

Probably the easiest nondeterministic code to get ahold of is the `math/rand` package, so for this tutorial, we'll look at how we would use Testify Mock to test code that uses randomness. We'll make a function that takes in an integer, and integer-divides (no decimal point) it by a number between 1 and 10.

```go
package main

import (
    "math/rand"
)

func divByRand(numerator int) int {
    return numerator / int(rand.Intn(10))
}
```

Pretty simple function, but since we called `rand.Intn`, which takes in a maximum value and returns a random integer up to that maximum, `divByRand` as a whole is nondeterministic; we have control over the numerator, but not the denominator.

But that's where Testify Mock package comes in; we can make it so in a given test, we _do_ get back the same value for `rand.Intn` every time.

First let's install Testify Mock with:

```
go get github.com/stretchr/testify/mock
```

Now we're ready to use Testify Mock to give `divByRand` some test coverage!

## ✨ Using Testify Mock

Now that we've got our nondeterministic code, here's the steps to making it testable with Testify Mock:

1. Take the nondeterministic piece of functionality and wrap it in a Go interface type.
2. Write an implementation of the interface that uses Testify Mock.
3. In the tests, use the mock implementation to select deterministic results of the functions your interface calls.

Besides injecting your mock into your code, the test is otherwise written like any other Go test.

Let's try it out on `divByRand`, step by step!

### 1. Take the nondeterministic function calls and wrap them in an interface

For `divByRand`, the nondeterministic code we're working with is `math/rand`'s `Intn` function, which takes in an integer and returns another integer. Here's what wrapping that method in an interface looks like:

```go
type randNumberGenerator interface {
    randomInt(max int) int
}
```

Now that we've got our interface, here's the plain implementation that calls the standard library's `rand.Intn`.

```go
// our standard-library implementation is an empty
// struct whose randomInt method calls math/rand.Intn
type standardRand struct{}

func (s standardRand) randomInt(max int) int {
    return rand.Intn(max)
}
```

Now that we've got one interface implementation, we can use that in our `divByRand` function like this:

```diff
  func divByRand(
      numerator int,
+     r randNumberGenerator,
  ) int {
-     return numerator / rand.Intn(10) 
+     return numerator / r.randomInt(10)
  }
```

We now would call divByRandom in our production code using a function call like `divByRandom(200, standardRand{})`.

In tests, though, we will instead use a mock implementation of our `randNumberGenerator` interface, which we'll write in the next section.

### 2. Write an implementation of the interface that uses Testify Mock

The Testify Mock package's main type is `Mock`, which handles the logic for mock API calls, such as:

* For each method being mocked, keeping track of how many times it was called, and with what arguments.
* Providing the coder with a way to specify return values to get back from the mock implementation when specified arguments are passed into given function calls.

We would first set up our mock implementation by embedding a Mock into a struct:

```go
type mockRand struct { mock.Mock }

func newMockRand() *mockRand { return &mockRand{} }
```
By embedding a `Mock`, now the `mockRand` type has the methods for registering an API call that you expect to happen in the tests.

For example, at the beginning of a test that uses the `randomNumberGenerator` interface, we could call:

```go
var m mockRand
m.On("randomInt", 10).Return(6)
```

which says "if the number 10 is passed in as the maximum number for this `mockRand`'s `randomInt` method, then always return the number 6".

In order to be able to use `m.On("randomInt", arg)` however, we will need to actually give our `mockRand` a `randomInt` method. Here's how we would do that:

```go
func (m *mockRand) randomInt(max int) int {
    args := m.Called(max)
    return args.Int(0)
}
```

We've got two lines of code, let's take a look at what they do. If it seems confusing at first, no worries; it will make more sense when we are actually using this code in a test.

1. In the first line when `randomInt` is called, we call [Mock.Called](https://pkg.go.dev/github.com/stretchr/testify/mock#Mock.Called) to record that it was called with the value passed in for `max`.
2. Additionally, `m.Called` returns an [Arguments](https://pkg.go.dev/github.com/stretchr/testify/mock#Arguments) object, which contains the return value(s) that we specified in the test to be returned if the function gets the given value of `max`. For example, if we called `m.On("randomInt", 20).Return(7)` in the test, `m.Called(20)` would return an `Arguments` object holding the return value 7.
3. Finally, to retrieve the return value, in the second line we call `args.Int(0)`, which means "return the zeroeth return value, and it will be of type int".

Note that in addition to [Arguments.Int](https://pkg.go.dev/github.com/stretchr/testify/mock#Arguments.Int), there's also [Bool](https://pkg.go.dev/github.com/stretchr/testify/mock#Arguments.Bool), [String](https://pkg.go.dev/github.com/stretchr/testify/mock#Arguments.String), and [Error](https://pkg.go.dev/github.com/stretchr/testify/mock#Arguments.Error) methods, which give us back the n-th return value in those types. And if you have a return value of another type, you would retrieve it with the [Arguments.Get](https://pkg.go.dev/github.com/stretchr/testify/mock#Arguments.Bool) method, which returns an `interface{}` you would then convert to the type you expect to get back.

### 3. In the tests, use the mock implementation and feed in results the function returns.

We've got our `mockRand` type implementing the `randomNumberGenerator` interface, so it can be passed into `divByRand` in Go code, including in our tests. The steps of our test now are:

1. Create an instance of your mock interface implementation.
2. Specify what results you want back when the mock's methods are called with a given set of arguments using the [On](https://pkg.go.dev/github.com/stretchr/testify/mock#Mock.On) and [Return](https://pkg.go.dev/github.com/stretchr/testify/mock#Call.Return) methods.
3. Run the code that's being tested, the standard way you would in a Go test.
4. Optionally, use methods like [Mock.AssertCalled](https://pkg.go.dev/github.com/stretchr/testify/mock#Mock.AssertCalled) to check that a given method indeed had been called during the test.

Here's what that looks like in code:

```go
package main

import (
    "testing"
)

func TestDivByRand(t *testing.T) {
    // get our mockRand
    m := newMockRand()
    // specify our return value. Since the code in divByRand
    // passes 10 into randomInt, we pass 10 in as the argument
    // to go with randomInt, and specify that we want the
    // method to return 6.
    m.On("randomInt", 10).Return(6)

    // now run divByRand and assert that we got back the
    // return value we expected, just like in a Go test that
    // doesn't use Testify Mock.
    quotient := divByRand(30, m)
    if quotient != 5 {
        t.Errorf("expected quotient to be 5, got %d", quotient)
    }

    // check that randomInt was called with the number 10;
    // if not then the test fails
    m.AssertCalled(t, "randomInt", 10)
}
```

Run `go test -v` and you'll get a passing test. But there's a bug in the original implementation of `divByRand`, so let's find it and give that bug some test coverage!

## 🐛 Finding and fixing a bug using Testify Mock

We wrote a test that utilizes Testify Mock, so now let's try using it to find a bug!

Since we're dividing by a nondeterministic value, we should have test coverage to make sure we can't accidentally divide by zero.

```go
func TestDivByRandCantDivideByZero(t *testing.T) {
    m := newMockRand()
    m.On("randomInt", int64(10)).Return(int64(0))

    quotient := divByRand(30, m)
    if quotient != 30 {
        t.Errorf("expected quotient to be 30, got %d", quotient)
    }
}
```

This time around, when we pass 10 into our mockRand's randomInt method, we return 0. So in divByRand, we end up dividing by 0.

Run `go test -v` and you should get:

```
--- FAIL: TestDivByRandCantDivideByZero (0.00s)
panic: runtime error: integer divide by zero [recovered]
	panic: runtime error: integer divide by zero
```

A panic from dividing by zero. Let's fix `divByRand` method to prevent this:

```diff
  func divByRand(n int, r randNumberGenerator) int {
-     return n / r.randomInt(10)
+     denominator := 1 + int(r.randomInt(10))
+     return n / denominator
  }
```

Now that we're passing 9 instead of 10 into our call to `randomInt`, we do need to update our test coverage to register a call to `randomInt(9)` instead of `randomInt(10)`, and for TestDivByRand, we need to return 5 instead of 6 since in divByRand we add 1 to the returned value.

```diff
  func TestDivByRand(t *testing.T) {
      var m mockRand
-     m.On("randomInt", 10).Return(6)
+     m.On("randomInt", 10).Return(5)
```

Run `go test -v` one more time and you should get...

```
=== RUN   TestDivByRand
--- PASS: TestDivByRand (0.00s)
=== RUN   TestDivByRandCantDivideByZero
--- PASS: TestDivByRandCantDivideByZero (0.00s)
PASS
ok  	github.com/andyhaskell/testify-mock	0.114s
```

Passing tests!

We've looked at how to use Testify Mock to write test coverage for randomness. In the next tutorial, we'll take a look at using Testify Mock to mock out a web API call.