Using mock on Golang tests

rafaacioly

Rafael Acioly

Posted on January 2, 2019

Using mock on Golang tests

I was studying golang these days and needed to write some tests.
I'm new at golang and i was not familiar with testing and stuffs like that so it was a little "hard" to me to get into it, this post is to make this easy for you as I wish it had been easy for me, i'll cover the basic principles to write tests with sql mock using the main golang structure (interfaces) to keep it simple.

I'm assuming that you're already familiar with golang

Golang interfaces

If you come from a language like java, PHP or any other OOP language you must be familiar with interfaces, in short an interface is a contract used to guarantee that a specific class implements determined method/attributes.
Go has interfaces too and is very simple to write it:

type repository interface {}

I'll use as example the Error interface that are built-in in go, as we know errors in go are a type or like some people say a first class citizen.
we can return a error, create a new type of error, pass error as parameter, store a error in a variable and so on...checkout the error interface
as you can see the Error interface only requires a single method called Error that returns a string, so if we do;

type MyCustomError struct {
    message string
}

func (e MyCustomError) Error() string {
    return e.message
}

we can use MyCustomError as an error type.


func myMethod() error {
    return MyCustomError{message: "my custom error"}
}

you see that our myMethod declares that the return of this method is a built-in error type and the method return a MyCustomError, this is possible because MyCustomError implements all methods required by the Error interface this way we can say that MyCustomError is a error type.

This is the main principle to write mocks in go, we create a custom struct that implementes the needed methods that provide all methods of a specific resource.

We'll not write an entire struct for sql.DB, thanks to God DATA-DOG we already have the package to do this job

Hands on

In the follow examples i'll let some TODOS in the code so you can see what is missing, i let you a challenge at the end, good luck.

Ok, know that we know a little about interfaces/structs let's dive in mocks, first create a new project with these files: database.go and test_database.go

the database.go file:

package database

import "database/sql"

type Repository interface {
    Find(id int) error
}

type repository struct {
    // the db field is of type `DB` from the `database` package
    // that provides the method to interact with our real database
    db *sql.DB
}

func NewRepository(db *sql.DB) Repository {
    return &repository{db: db}
}

// Find retrieves a single record from database
// based on id column
func (r repository) Find(id int) error {}

Notes:
The NewRepository method declares that will return a Repository interface and accept the sql.DB struct as parameter, this interface is only for learning propose, we do not have to write it in this example.

Let's start with the Find method, in your database_test file write the follow code:

package database

import (
    "gopkg.in/DATA-DOG/go-sqlmock.v1"
    "testing"
)

func TestFindDatabaseRecord(t *testing.T) {
    // the db satisfy the sql.DB struct
    db, mock, err := sqlmock.New()
    if err != nil {
        t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
    }
    defer db.Close()

    // our sql.DB must exec the follow query
    mock.ExpectQuery("SELECT \\* FROM my_table").
        // the number 3 must be on the query like "where id = 3"
        WithArgs(3)
        // TODO: define the values that need to be returned from the database

    myDB := NewRepository(db) // passes the mock to our code

    // run the code with the database mock
    if err = myDB.Find(3); err != nil {
        t.Errorf("something went wrong: %s", err.Error())
    }
}

Resume:
The code above create a mock of sql.DB struct and passes it as parameter to our code, the mock says that the query SELECT * FROM my_table must be executed with the argument 3, the DATA-DOG package provides a lot of methods like ExpectExec, WillReturnResult and ExpectRollback, the package is smart enough to know when you run a specific method and will compare your expectations if needed.

the Find method:


func (r repository) Find(id int) error {
    // r is the reference to our struct and db is the mock that we passed
    stmt, err := r.db.Prepare("SELECT * from my_table WHERE id = $1")
    if err != nil {
        return err
    }
    defer stmt.Close()

    // change the placeholder $1 to use the id parameter
    _, err = stmt.Exec(id) // should be result, err = stmt.Exec(id)
    if err != nil {
        return err
    }

    // TODO: we need to fill a struct with the database result
    // and return it with nil

    return nil // should be mystruct, nil
}

You have noted that the Find method doesn't return any database information, the examples of this code is very generic with the purpose of you implement it, can you handle this?

What you have to todo from here:

  1. return the data on the Find method along with the error
  2. compare the returned values

hints:

  • take a look at the method NewRows and the WillReturnRows.
  • create a struct to hold the database values and return this struct on Find method

when are finished share with me what you have done! :)

Thank you for reading till here, if you have any advice, questions or suggestion i'll be glad to talk about it.

💖 💪 🙅 🚩
rafaacioly
Rafael Acioly

Posted on January 2, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related