Unit Tests & Mocking: the Bread and the Butter
Tags: go, testing, vscode, webdev
Published: 2024-07-12 08:36:20


## Premise

Welcome back, folks 🤝 After a while, I finally took the time to start this new series of blog posts around computer science, programming, and Go. This series will be focused on different things we can deal with when dealing with our daily work.

### How you should read it

Before getting into it, let me share how this blog post (and the upcoming ones) is meant to be read. This blog post targets a specific subject 📍. Therefore, I suggest you read other resources to gain a broader overview. It aims to be your starting point to further dig into the topic. For sure, I'll manage to share resources whenever necessary. Finally, there won't be any GitHub repositories since the code will be pretty focused and not part of a project. Now, you're ready to embark on the journey with unit testing and Go.

## Unit Tests & Mocking

The goal is to be able to write a unit test for a struct that holds a dependency toward another one. Unit testing is a technique in which you test a specific **Unit** by mocking its dependencies. Let's use an example to better depict it. We're going to write a test for the `billing` package. This could be one of the several packages in your codebase. For the sake of the demo, everything has been written to the `billing.go` file (do not do this in production). Within this file, we defined these types:

- The `Invoice` struct model, holding the `CreatedAt` and `Amount` fields
- The `Archiver` interface represents the dependency 💉 our UUT relies on
- The `InvoiceManager` struct is our **Unit Under Test** model
- The `Store` struct implementing the `Archiver` interface

Before showing the code, let me share this drawing to let you understand better the actors:

<p align="center">
    <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/day7821kbexs8htuttmz.png" alt="billing package types">
</p>

The complete source code looks like this:

```go
package billing

import (
 "errors"
 "fmt"
 "os"
 "time"

 "github.com/google/uuid"
)

type Invoice struct {
 CreatedAt time.Time
 Amount    float64
}

type InvoiceManager struct {
 Archiver Archiver
}

func (i *InvoiceManager) RecordInvoice(invoice Invoice) (err error) {
 id, err := i.Archiver.Archive(invoice)
 if err != nil {
  return err
 }
 fmt.Fprintf(os.Stdout, "recorded invoice with id: %s\n", id)
 return nil
}

type Archiver interface {
 Archive(invoice Invoice) (id string, err error)
}

type Store struct{}

func (s *Store) Archive(invoice Invoice) (id string, err error) {
 if invoice.Amount < 0 {
  return "", errors.New("amount cannot be less than 0")
 }
 // logic omitted for brevity
 return uuid.NewString(), nil
}
```

### The Unit Under Test 🤠

With the previous code in mind, our task is to write a test for the method `RecordInvoice` based on the `InvoiceManager` receiver type. The function can take two paths:

1. The happy path is when the `i.Archiver.Archive` invocation doesn't return any error
1. The "unhappy" path is when it gives back an error

As good developers, we are asked to write two unit tests. However, we'll write only the happy path test since we would like to focus more on the steps to get there. After all, everybody knows how to write unit tests for this small piece of code.

### First things first: mocking

The `InvoiceManager` struct depends on the `Archiver` interface. Let's see how we can mock it. Here, we have two options: hand-writing it or automatically generating it. We opt for the latter even if the project's size is trivial.

> Be aware that, in real-life projects, this method can save you a consistent amount of time.

#### Mockery tool

We'll take advantage of this tool, which can be downloaded [here](https://vektra.github.io/mockery/latest/). Before proceeding, make sure you have correctly installed it on your machine. To confirm it, you can run this command in your terminal:

```shell
mockery --version
```

In your terminal, navigate to the root folder of your project and run the following command:

```shell
mockery --dir billing/ --name Archiver --filename archiver.go
```

This command specifies three parameters:

- `--dir` is the directory where to look for interfaces to mock: our code is contained within the `billing` folder
- `--name` is the name of the interface to mock: `Archiver` is the identifier for our interface
- `--filename` is the filename for the mock file: we used `archiver.go` to keep the naming convention consistent

When you run the command, you'll notice a new `mock` folder has been created. You'll find the `archiver.go` mock file inside it. By default, the `mockery` tool creates a new folder (and a new package called `mock`) to hold all the mocks.  
> If you're not good with this approach, you can override it when running the tool.  
  
Based on my experience, I think the default behavior might be applied in almost all cases. You might also notice that the compiler started to complain. This is due to missing packages in your project. The fix is easy. You should just run the command `go mod tidy` where your `go.mod` file is located. Then, double-check the errors have gone away, and you'll be ready to use the mocks.

### The test code

Let's see how we can exploit the scaffolded mock in our unit tests. By the books, a unit test should have three stages: Arrange, Act, and Assert (aka *AAA* paradigm). We'll cover each of these in the subsequent sections. First, I'm going to show you the code, and then I'll walk you through all the relevant parts of it:

```go
package billing_test

import (
 "testing"
 "time"

 "github.com/ossan-dev/unittestmock/billing"
 "github.com/ossan-dev/unittestmock/mocks"
 "github.com/stretchr/testify/assert"
)

func TestRecordInvoice(t *testing.T) {
 // Arrange
 invoice := billing.Invoice{CreatedAt: time.Now(), Amount: 66.50}
 store := mocks.NewArchiver(t)
 store.On("Archive", invoice).Return("16668b88-34a0-4a25-b1da-6a1875072802", nil).Once()
 uut := &billing.InvoiceManager{
 Archiver: store,
 }
 // Act
 err := uut.RecordInvoice(invoice)
 // Assert
 assert.NoError(t, err)
 store.AssertExpectations(t)
}
```

#### Arrange 🧱

Here, we've to invoke the function `NewArchiver` from the `mocks` package to get a new instance of our mock. Then, we set it up by using three methods:

1. The `On` method specifies to which invocation this mock has to reply (also with what arguments)
1. The `Return` method specifies which values to return from the mock when invoked
1. The `Once` specifies how many times to return values from this mock

Lastly, we instantiate our **UUT** by passing in the `store` mock as the `Archiver` interface. We can safely proceed.

#### Act 👊

Within this stage, we invoke the method `RecordInvoice` defined on the `uut` variable. No further explanations are needed here.

#### Assert 🤞

In this final stage, we have to check two things:

1. The `uut` variable gives back whatever we expect. In this case, we expect a `nil` error
1. The `store` mock behaves as expected

The second point means that the method `Archive` has been invoked with the expected arguments, the correct number of times, and so on.

## Run Test

Now, we can safely run our test. To run it, we invoke the command:

```shell
go test -v ./billing
```

And that's the outcome on my machine:

```text
=== RUN   TestRecordInvoice
recorded invoice with id: 16668b88-34a0-4a25-b1da-6a1875072802
--- PASS: TestRecordInvoice (0.00s)
PASS
ok      github.com/ossan-dev/unittestmock/billing       0.004s
```

## That's a Wrap

I hope you found this blog post helpful. As you may imagine, there are several things to cover regarding unit testing, mocking, etc. Any feedback is highly appreciated.  
  
Before leaving, I strongly invite you to reach out if you are interested in some topics and want me to address them. I swear I shortlist them and do my best to deliver helpful content.

Thank you very much for the support, and see you in the next one 👋