In this article, we are going to unit test our application by mocking some components in order to isolate the modules that we want to test. In the previous article we refactored our API implement a Clean Architecture approach so we created all these interfaces

We created a router interface so our application is independent of the HTTP framework. We created two implementations for the router interface, one using MUX and other using CHI, but we can also create our own implementation of that router interface. This HTTP router delegates the requests to the controller layer and the controller collaborates with the service layer and the service layer is the place where the business logic is implemented. The service layer interacts with the repository, this repository could be a Firestore database, MongoDB, MySQL, Dynamodb, Postgres or any other database. This repository could be also a mock repository and that’s what we are going to use to isolate our PostService in order to test it.
Install Testify library
We are going to use Testify to create the mock repository that is going to be an implementation of the post repository interface.
$ go get -u github.com/stretchr/testify
go: added github.com/stretchr/testify v1.8.2
Testify will provide us assertion methods or mocking methods.
Service Unit Test
We are going to create unit test for PostService interface implementation by creating new file named post-service_test.go. And the naming convention here is _test.go for all testing files.
type PostService interface {
Validate(post *entity.Post) error
Create(post *entity.Post) (*entity.Post, error)
FindAll() ([]entity.Post, error)
}
First test is going to be for the Validate method, actually we have two cases here when we receive an empty post or nil as the post argument and when we receive an empty title. So we are going to create two test cases one for each scenario.
Unit Test for Validate method
func TestValidateEmptyPost(t *testing.T) {
testService := NewPostService(nil)
err := testService.Validate(nil)
assert.NotNil(t, err)
assert.Equal(t, "The post is empty", err.Error())
}
func TestValidateEmptyPostTitle(t *testing.T) {
post := entity.Post{ID: 1, Title: "", Text: "B"}
testService := NewPostService(nil)
err := testService.Validate(&post)
assert.NotNil(t, err)
assert.Equal(t, "The post title is empty", err.Error())
}
Mocking for PostRepository
We are going to create our mock repository so we need to create a structure for this mock repository and also to do the implementation of the PostRepository interface.
type MockRepository struct {
mock.Mock
}
func (mock *MockRepository) Save(post *entity.Post) (*entity.Post, error) {
args := mock.Called()
result := args.Get(0)
return result.(*entity.Post), args.Error(1)
}
func (mock *MockRepository) FindAll() ([]entity.Post, error) {
args := mock.Called()
result := args.Get(0)
return result.([]entity.Post), args.Error(1)
}
Here what we need to do is actually stub functions returning the arguments that we receive. We can access those arguments by using mock.Called(), and this is going to return the arguments and now we need to return from this function. In this case the first the first argument is going to be the post so basically we can access the first argument like this args.Get(0) , also using args.Error(1) to return the error entity.
Unit Test for FindAll method
func TestFindAll(t *testing.T) {
mockRepo := new(MockRepository)
var identifier int64 = 1
post := entity.Post{ID: identifier, Title: "A", Text: "B"}
// Setup expectations
mockRepo.On("FindAll").Return([]entity.Post{post}, nil)
testService := NewPostService(mockRepo)
result, _ := testService.FindAll()
// Mock Assertion: Behavioral
mockRepo.AssertExpectations(t)
// Data Assertion
assert.Equal(t, identifier, result[0].ID)
assert.Equal(t, "A", result[0].Title)
assert.Equal(t, "B", result[0].Text)
}
Unit Test for Create method
func TestCreate(t *testing.T) {
mockRepo := new(MockRepository)
post := entity.Post{Title: "A", Text: "B"}
// Setup expectations
mockRepo.On("Save").Return(&post, nil)
testService := NewPostService(mockRepo)
result, err := testService.Create(&post)
mockRepo.AssertExpectations(t)
assert.NotNil(t, result.ID)
assert.Equal(t, "A", result.Title)
assert.Equal(t, "B", result.Text)
assert.Nil(t, err)
}
Unit Test of Full Implementation
package service
import (
"golang-mux-api/entity"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
type MockRepository struct {
mock.Mock
}
func (mock *MockRepository) Save(post *entity.Post) (*entity.Post, error) {
args := mock.Called()
result := args.Get(0)
return result.(*entity.Post), args.Error(1)
}
func (mock *MockRepository) FindAll() ([]entity.Post, error) {
args := mock.Called()
result := args.Get(0)
return result.([]entity.Post), args.Error(1)
}
func TestFindAll(t *testing.T) {
mockRepo := new(MockRepository)
var identifier int64 = 1
post := entity.Post{ID: identifier, Title: "A", Text: "B"}
// Setup expectations
mockRepo.On("FindAll").Return([]entity.Post{post}, nil)
testService := NewPostService(mockRepo)
result, _ := testService.FindAll()
// Mock Assertion: Behavioral
mockRepo.AssertExpectations(t)
// Data Assertion
assert.Equal(t, identifier, result[0].ID)
assert.Equal(t, "A", result[0].Title)
assert.Equal(t, "B", result[0].Text)
}
func TestCreate(t *testing.T) {
mockRepo := new(MockRepository)
post := entity.Post{Title: "A", Text: "B"}
// Setup expectations
mockRepo.On("Save").Return(&post, nil)
testService := NewPostService(mockRepo)
result, err := testService.Create(&post)
mockRepo.AssertExpectations(t)
assert.NotNil(t, result.ID)
assert.Equal(t, "A", result.Title)
assert.Equal(t, "B", result.Text)
assert.Nil(t, err)
}
func TestValidateEmptyPost(t *testing.T) {
testService := NewPostService(nil)
err := testService.Validate(nil)
assert.NotNil(t, err)
assert.Equal(t, "The post is empty", err.Error())
}
func TestValidateEmptyPostTitle(t *testing.T) {
post := entity.Post{ID: 1, Title: "", Text: "B"}
testService := NewPostService(nil)
err := testService.Validate(&post)
assert.NotNil(t, err)
assert.Equal(t, "The post title is empty", err.Error())
}
How to run Unit Test
Test (specific test)
go test -run NameOfTest
Test (all the tests within the service folder)
go test service/*.go
Download Source Code
$ git clone https://github.com/favtuts/golang-mux-api.git
$ cd golang-mux-api$ git checkout service-unit-test
$ go build$ go run .