Video:

Unit Testing our Rocket Service using Mocks

April 22, 2021

Course Instructor: Elliot Forbes

Hey Gophers! My name is Elliot and I'm the creator of TutorialEdge and I've been working with Go systems for roughly 5 years now.

In the last video, we created our Rocket service which will manage our inventory of rockets by making calls to the backend store. At this point though, we want to start adding some basic unit tests to our application and test that the logic in our Rocket service works as intended.

Installing Mockgen

We’ll need to install two dependencies prior to us being able to generate the mocks we need in order to run our test.

$ go get github.com/golang/mock/gomock
$ go get github.com/golang/mock/mockgen

Mocking our Database

We haven’t yet implemented our db package yet, so we don’t have a concrete implementation that will allow us to add and remove rockets from our backend database.

We do however, have an interface that we can use to generate mocked code which can then be used within our unit tests for our rocket service.

We’ll want to use the mockgen library in order to generate our mocked code. Let’s add the compiler directives at the start of our rocket.go file that mockgen will then be able to generate our mocks from:

//go:generate mockgen -destination=rocket_mocks_test.go -package=rocket github.com/TutorialEdge/go-grpc-services-course/internal/rocket Store

package rocket

import "context"

// Rocket - should contain the definition of our
// rocket
type Rocket struct {
	ID      string
	Name    string
	Type    string
	Flights int
}

// the rest of our rocket.go implementation

With this in place, let’s try and generate our mocks now using the following command:

$ go generate ./...

This should execute successfully and we should be left with a new file called rocket_mocks_test.go within our internal/rocket directory.

Implementing our Unit Tests

Now that our mocks are ready to go, let’s set about creating some unit tests to validate our service implementation.

Create a new file called rocket_test.go under internal/rocket and let’s add some tests:

package rocket

import (
	"context"
	"testing"

	gomock "github.com/golang/mock/gomock"
	"github.com/stretchr/testify/assert"
)

func TestRocketService(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	t.Run("tests get rocket by id", func(t *testing.T) {
		rocketStoreMock := NewMockStore(mockCtrl)
		id := "UUID-1"
		rocketStoreMock.
			EXPECT().
			GetRocketByID(id).
			Return(Rocket{
				ID: id,
			}, nil)

		rocketService := New(rocketStoreMock)
		rkt, err := rocketService.
			GetRocketByID(
				context.Background(),
				id,
			)

		assert.NoError(t, err)
		assert.Equal(t, "UUID-1", rkt.ID)
	})

	t.Run("tests insert rocket", func(t *testing.T) {
		rocketStoreMock := NewMockStore(mockCtrl)
		id := "UUID-1"
		rocketStoreMock.
			EXPECT().
			InsertRocket(Rocket{
				ID: id,
			}).
			Return(Rocket{
				ID: id,
			}, nil)

		rocketService := New(rocketStoreMock)
		rkt, err := rocketService.
			InsertRocket(
				context.Background(),
				Rocket{
					ID: id,
				},
			)

		assert.NoError(t, err)
		assert.Equal(t, "UUID-1", rkt.ID)
	})

	t.Run("tests delete rocket", func(t *testing.T) {
		rocketStoreMock := NewMockStore(mockCtrl)
		id := "UUID-1"
		rocketStoreMock.
			EXPECT().
			DeleteRocket(id).
			Return(nil)

		rocketService := New(rocketStoreMock)
		err := rocketService.
			DeleteRocket(
				id,
			)

		assert.NoError(t, err)
	})
}

With these tests in place, let’s try running them and see if our tests are passing:

$ go test ./... -v
?       github.com/TutorialEdge/go-grpc-services-course/cmd/server      [no test files]
=== RUN   TestRocketService
=== RUN   TestRocketService/tests_get_rocket_by_id
=== RUN   TestRocketService/tests_insert_rocket
=== RUN   TestRocketService/tests_delete_rocket
--- PASS: TestRocketService (0.00s)
    --- PASS: TestRocketService/tests_get_rocket_by_id (0.00s)
    --- PASS: TestRocketService/tests_insert_rocket (0.00s)
    --- PASS: TestRocketService/tests_delete_rocket (0.00s)
PASS
ok      github.com/TutorialEdge/go-grpc-services-course/internal/rocket 0.130s

Conclusion

Perfect, we’ve been able to build a little bit more confidence in our system by adding a few tests around our core business logic.

Adding tests often and early is critical to building solid, reliable systems that won’t fail you in production and whilst this is a course around gRPC, it is vital we uphold these standards at all times!