loading...

Mocking Methods in Go

dmigwi profile image Migwi Ndung'u ・3 min read

It goes without saying that a battalion of soldiers is as strong as their weakest link. In this context, a piece of code is free of bugs and other issues as its weakest point. Kent Beck from facebook once visited us at Andela to give a talk on TDD and throughout his session, it was clear that unit testing is critical part of the software development.

Languages like Python provide standardised libraries and frameworks that make it easy to mock methods and functions inside an impure function or method. With python unit tests are pretty easy thus achieving a 100% test coverage is the norm.

Go or Golang is a light weight language that is not C based. Its syntax is pretty easy, I like to refer to golang as a "Pythonized version of Java" due to the similarity of concepts imported from both Java and Python. Golang does not have an official package that supports mocking of methods during unit testing.

Mocking in golang is done with the help of interfaces. Mocking in unit testing is important as it ensure that variables, methods and functions modified outside the scope of the function being tested do not affect the test output.

Here is the implementation of mocking.go

/** mocking.go **/

package main

import "fmt"

type (
    // Values defines interface to be used in mocking
    Values interface {
        GetVolume() int
        GetSurfaceArea() int
    }

    // Measurements defines the measurements used to calculate
    // the surface area and volume of a cube or a cuboid.
    Measurements struct {
        Length int
        Width  int
        Height int
    }
)

// GetVolume calculates and returns the volume of a cube or a cuboid
func (config *Measurements) GetVolume() int {
    return config.Height * config.Length * config.Width
}

// GetSurfaceArea calculates and returns the surface area of a cube or cuboid
func (config *Measurements) GetSurfaceArea() int {
    return (config.Length * config.Width * 2) +
        (config.Length * config.Width * 2) +
        (config.Height * config.Width * 2)
}

// GetVolumeAndArea fetches and returns the volume and the area
func GetVolumeAndArea(val Values) (int, int) {
    return val.GetVolume(), val.GetSurfaceArea()
}

// program execution starts here
func main() {
    var (
        area, volume int

        data = &Measurements{
            Length: 1,
            Height: 5,
            Width:  3,
        }
    )

    volume, area = GetVolumeAndArea(data)

    fmt.Printf("Volume : %d , Area : %d \n", volume, area)
}

GetVolumeAndArea function accepts an interface as its input parameter. This means that any receiver (struct used to create a method) that implements GetVolume and GetSurfaceArea also implements the Values interface.

Mocking of GetVolume and GetSurfaceArea is done by passing their customized implementations through the struct that acts as their receiver.

Below is how mocking of GetVolume and GetSurfaceArea should be done using interfaces. No extra libraries or frameworks needed.

/** mocking_test.go **/

package main

import (
    "strconv"
    "testing"
)

// MockTest helps implement our customized GetVolume and GetSurfaceArea
// needed to mock the original implementation in mocking.go
type MockTest struct {
    Elem int
}

// GetVolume returns the volume of a cube
func (config *MockTest) GetVolume() int {
    return config.Elem * config.Elem * config.Elem
}

// GetSurfaceArea returns the surface area of a cube
func (config *MockTest) GetSurfaceArea() int {
    return config.Elem * config.Elem * 6
}

// TestGetVolumeAndArea tests the functionality of GetVolumeAndArea
func TestGetVolumeAndArea(t *testing.T) {
    for key, val := range map[MockTest][]int{
        MockTest{Elem: 5}: []int{125, 150},
        MockTest{Elem: 2}: []int{8, 24},
        MockTest{Elem: 1}: []int{1, 6},
    } {
        t.Run("Cube side length "+strconv.Itoa(key.Elem), func(t *testing.T) {
            volume, area := GetVolumeAndArea(&key)

            if volume != val[0] {
                t.Errorf("Expected volume to be equal to %d but was equal to %d ",
                    volume, val[0])
                t.FailNow()
            }

            if area != val[1] {
                t.Errorf("Expected area to be equal to %d but was equal to %d ",
                    area, val[1])
                t.FailNow()
            }
        })
    }
}

Apart from mocking, interfaces also helps in separating of concerns such that pieces of code/functions/methods that communicate with each other can be made to run independent of each other.

They also help hide the underlying implementations that should not be exposed to the outside world especially when building an API.

For any Clarifications, Corrections or Complements reach out to me via Email on mailto:migwindungu0@gmail.com

Posted on by:

dmigwi profile

Migwi Ndung'u

@dmigwi

My Bitcoin addresses: 3EQSpgHjirbjehML41vCnm34k1xeMovUVu

Discussion

pic
Editor guide
 

I'm a bit confused. If you mock a function or method, and test that mock, how can we then say that the test is a test of the original function or method? You're essentially testing the mock, and not the original function or method, no?