Original post

Ankur Anand

has a built-in framework provided by the testing package, that makes writing tests easier, but how do we write a more complicated test that requires ?

In this post, we will learn how to take advantage of structs and interfaces in Go to mock any service or library you might be using, without using any 3rd party tools and libraries.

We will start by defining our system to understand what we are going to test and mock.

System

Example System and Integration.

Our system has two components.

  1. “Our Service” that we own and build.
  2. “Third-party service or library” that interacts with some database and we use the functionality of it in our service.

Now Since we are building “Our Service” we want to write an independent unit test for “Our Service” but as we use the functionality of third-party service or library in our service if we test without mock’s we will be doing the integration testing which is sometimes complex and more time-consuming.

For our demonstration purpose, we’ll write a simple library that checks if a user exists or not inside a map. Our Service will use this to carry out its business logic and this will become the third-party library inside our system.

Third-party Library Code

Service

Code:

If you take a look at theRegisterUser function:

// RegisterUser if the user is not registered before
func RegisterUser(user User) error {
if userdb.UserExist(user.Email) {
return fmt.Errorf("email '%s' already registered",
user.Email)
}
// ...code for registering the user...
log.Println(user)
return nil
}

It calls the function userdb.UserExist which is provided by our third-party library and currently we cannot unit test our RegisterUser function without doing a third party call.

Mocks

About mocking:

Mock objects meet the interface requirements of, and stand in for, more complex real ones. Thank you, Wikipedia!

Let’s break it up.

  1. Mock objects meet the interface requirements.

To do so we have to refactor our service code. First, we have to define our interface requirement that our mock going to implement. In our case, we need an interface that is just internal to the package and provides a way to test if a user exists or not.

// registrationPreChecker validates if user is allowed to register.
type registrationPreChecker interface {
userExists(string) bool
}

Interface Implementation.
userExists function of our new defined interface simply wraps the call to the actual call to third party service.

type regPreCheck struct {}func (r regPreCheck) userExists(email string) bool {
return userdb.UserExist(email)
}

Next, we will create a package-level variable of type registrationPreChecker and assign an instance of regPreCheck to it within init function.

var regPreCond registrationPreChecker

func init() {
regPreCond = regPreCheck{}
}

As regPreCond is of type registrationPreChecker which validates the user exists or not, we can use this inside our RegisterUser function. So instead of directly calling userdb.UserExist function inside RegisterUser function we will call it through our interface implementation.

// check if user is already registered
found := regPreCond.userExist(user.Email)

Refactored code:

If we run the go test again it passes because we haven’t changed any behavior of our function. But let’s see how this makes unit testing of our service so easy—through mocks.

Writing mocks

Mock objects meet the interface requirements.

Here our’s mock object implements the registrationPreChecker interface.

type preCheckMock struct{}

func (u preCheckMock) userExists(email string) bool {
return userExistsMock(email)
}

Mock implementation is returning an userExistsMock function type here instead of directly returning true or false. This helps in assigning mock at runtime instead of compile-time. You can see this in the TestRegisterUser function.

2. and stand in for, more complex real ones

regPreCond = preCheckMock{}

We simply assigned our regPreCond of type registrationPreChecker which validates the user exists or not with our mock implementation during runtime of our test. As you can see in TestRegisterUser function.

func TestRegisterUser(t *testing.T) {
user := User{
Name: "Ankur Anand",
Email: "anand@example.com",
UserName: "anand",
}

regPreCond = preCheckMock{}
userExistsMock = func(email string) bool {
return false
}

err := RegisterUser(user)
if err != nil {
t.Fatal(err)
}

userExistsMock = func(email string) bool {
return true
}
err = RegisterUser(user)
if err == nil {
t.Error("Expected Register User to throw and error got nil")
}
}