testing | Module Safari https://modulesafari.com development simplified Sat, 07 Dec 2019 02:28:17 +0000 en-US hourly 1 https://wordpress.org/?v=5.5.3 https://modulesafari.com/wp-content/uploads/2019/11/favivon.ico testing | Module Safari https://modulesafari.com 32 32 Mockery; Mock Everything In Go. https://modulesafari.com/mockery-mock-everything-in-go/ https://modulesafari.com/mockery-mock-everything-in-go/#comments Mon, 02 Dec 2019 01:48:41 +0000 https://modulesafari.com/?p=658 Mockery is an awesome tool that offers the ability to generate mocks from Go interfaces. Additionally, it’s output uses the testify framework, so you can easily plug in your mocks and perform granular tests on your code. When combined with the power of a Makefile, it makes mocking a breeze. The Makefile Some people might […]

The post Mockery; Mock Everything In Go. appeared first on Module Safari.

]]>
Mockery is an awesome tool that offers the ability to generate mocks from Go interfaces. Additionally, it’s output uses the testify framework, so you can easily plug in your mocks and perform granular tests on your code. When combined with the power of a Makefile, it makes mocking a breeze.

The Makefile

Some people might be turned off by the use of a makefile. However, it is going to make this significantly more convenient, especially as your project grows.

GOC=go
GO111MODULE=on

DIRECTORIES=$(sort $(dir $(wildcard pkg/*/*/)))
MOCKS=$(foreach x, $(DIRECTORIES), mocks/$(x))

.PHONY: all test clean-mocks mocks

all: demo

demo:
	$(GOC) build -o demo

test: | mocks
	go test ./...

clean-mocks:
	rm -rf mocks

mocks: $(MOCKS)
	
$(MOCKS): mocks/% : %
	mockery -output=$@ -dir=$^ -all

This makefile takes all of the subdirectories which match the pattern “pkg/*/*” and then matches them with an output to be in the folder mocks/pkg/... . For each of these pairs, it makes a call to mockery with the -all flag. This flag tells it to mock all of the interfaces in a directory.

The Go Code to Mock

For this example, we will have a Teller struct, which exports a method Tell. This method takes in a story and reads it chapter by chapter to an audience far and wide. Here is what the story interface and Teller struct look like.

type Story interface {
	ReadChapter(chapter int) ([]byte, error)
	HasChapter(chapter int) bool
}
type Teller struct {
}

func (teller Teller) say(words []byte) {
	//Pretend this makes it talk
}

func (teller Teller) Tell(story Story) error {
	for i := 0; story.HasChapter(i); i++ {
		words, err := story.ReadChapter(i)
		if err != nil {
			return err
		}
		teller.say(words)
	}
	return nil
}

How can you make sure that the Teller reads every single chapter until there are no more in the story? You mock the story! First, you must generate the mocks with the command make mocks (or you can just run the test with make test, it knows to make the calls to mockery first). Here is our tester code for the Teller.

import (
	"testing"

	demoMock "github.com/moduledemos/mockery-demo/mocks/pkg/demo"

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

func TestTeller_Tell(t *testing.T) {
	teller := Teller{}
	story := new(demoMock.Story)
	noChapters := 20
	story.On("ReadChapter", mock.Anything).Return(
		[]byte(":)"), nil).Times(noChapters)
	for i := 0; i < noChapters; i++ {
		story.On("HasChapter", i).Return(true).Once()
	}
	story.On("HasChapter", noChapters).Return(false).Once()
	err := teller.Tell(story)
	assert.NoError(t, err)

	story.AssertExpectations(t)
}

You can see that we can mock different return values for the HasChapter function based on the input given to it. Additionally, you might notice .Times(n) or .Once appended to the call to .On. When combined with the call to AssertExpectations at the end, this enforces the number of times each variation is called.

If it does not matter what the input is, you can use the global mock.Anything from the testify library to match with any given input. With all of these features, I was able to create a test that ensured that all of the chapters would be read until the function HasChapter returned false. All of this, in just ~12 lines of code. If you like doing more work with less code, then I highly recommend using mockery and testify for all of your Go testing needs.

The post Mockery; Mock Everything In Go. appeared first on Module Safari.

]]>
https://modulesafari.com/mockery-mock-everything-in-go/feed/ 1
Testify; Test Your Go Code https://modulesafari.com/testify-test-your-go-code/ https://modulesafari.com/testify-test-your-go-code/#respond Sun, 01 Dec 2019 19:36:30 +0000 https://modulesafari.com/?p=634 Testify is a testing library that makes writing tests for your Go code easy. It reduces the amount of code you need for tests, while also providing extra functionality for easy mocking. Especially when combined with mockery, you really have a Golang testing powerhouse. Here is a direct link to the Github repository if you […]

The post Testify; Test Your Go Code appeared first on Module Safari.

]]>
Testify is a testing library that makes writing tests for your Go code easy. It reduces the amount of code you need for tests, while also providing extra functionality for easy mocking. Especially when combined with mockery, you really have a Golang testing powerhouse. Here is a direct link to the Github repository if you are interested.

Assert

Testify’s assert package contains functions that make checking certain conditions easy. They replace the normal if statement list flow and have reflection built-in for comparisons between structs. For this example, let’s say we are making a function fib, which calculates the number in the nth place in the Fibonacci Sequence. Here is our naive implementation of this, which has a few issues.

func fib(n int64) int64 {
	var a int64 = 0
	var b int64 = 1
	for i := int64(0); i < n-1; i++ {
		tmp := b
		b = a + b
		a = tmp
	}
	return b
}

We want to be sure that this implementation is correct, so we add some tests for it using testify.

package main

import(
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestFib(t *testing.T) {
	assert.Equal(t, int64(0), fib(0))
	assert.Equal(t, int64(1), fib(1))
	assert.Equal(t, int64(1), fib(2))
	assert.Equal(t, int64(6765), fib(20))
}

Now, we run our tests with go test and we can immediately see an issue.

--- FAIL: TestFib (0.00s)
    fib_test.go:12: 
        	Error Trace:	fib_test.go:10
        	Error:      	Not equal: 
        	            	expected: 0
        	            	actual  : 1
        	Test:       	TestFib
FAIL

Thanks to testify, we easily see that on line 10, the output of the function was 1, instead of 0. We go to our code and see that we don’t handle the case of n == 0, and fix the issue.

func fib(n int64) int64 {
	var a int64 = 0
	var b int64 = 1
	for i := int64(0); i < n; i++ {
		tmp := b
		b = a + b
		a = tmp
	}
	return a
}

The tests passed, and we can be more confident that our code is correct.

Require

While assert is great for most cases, sometimes you have dependencies in your test function. There, you want to make sure that when a test fails, it reports only what failed. Enter the require package. Require feels like a carbon copy of the assert package, with just one key difference: it calls t.FailNow(). Let’s make a function which uses the Fibonacci function from earlier, and outputs all terms up to n in an array.

func fibAll(n int64) []int64 {
	if n > 0 {
		return nil
	}
	out := make([]int64, n + 1)
	for i := int64(0); i <= n; i++ {
		out[i] = fib(i)
	}
	return out
}

This function needs some tests, so let’s write some up real quick. We want to do some spot checking to make sure that the sequence is generated correctly.

func TestFibAll(t *testing.T) {
	for i := int64(15); i < 40; i++ {
		fibNumbers := fibAll(i)
		assert.NotNil(t, fibNumbers)
		assert.Len(t, fibNumbers, int(i+1))
		assert.Equal(t, 233, fibNumbers[13])
		assert.Equal(t, 55, fibNumbers[10])
	}
}

We run our tests and get back this messy error.

--- FAIL: TestFibAll (0.00s)
    fib_test.go:19: 
        	Error Trace:	fib_test.go:19
        	Error:      	Expected value not to be nil.
        	Test:       	TestFibAll
    fib_test.go:20: 
        	Error Trace:	fib_test.go:20
        	Error:      	"[]" should have 16 item(s), but has 0
        	Test:       	TestFibAll
panic: runtime error: index out of range [13] with length 0 [recovered]
	panic: runtime error: index out of range [13] with length 0

goroutine 21 [running]:
testing.tRunner.func1(0xc000116200)
	/snap/go/4762/src/testing/testing.go:874 +0x3a3
panic(0x719700, 0xc0000d42a0)
[Full stack trace omitted]
exit status 2
FAIL

As you can see, the tests reported 2 failures instead of just one, and the code panicked. Of course, this is still usable and would be significantly better than nothing. However, if the unit tests were more complex, it can start to become unclear as to what the actual problem is. So, let’s put “require” to use.

func TestFibAll(t *testing.T) {
	for i := int64(15); i < 40; i++ {
		fibNumbers := fibAll(i)
		require.NotNil(t, fibNumbers)
		require.Len(t, fibNumbers, int(i+1))
		assert.Equal(t, 233, fibNumbers[13])
		assert.Equal(t, 55, fibNumbers[10])
	}
}

When we run our tests, it becomes very clear what the issue is.

--- FAIL: TestFibAll (0.00s)
    fib_test.go:20: 
        	Error Trace:	fib_test.go:20
        	Error:      	Expected value not to be nil.
        	Test:       	TestFibAll
FAIL

This is a significant improvement. Especially as the complexity of a codebase increases, simplified test failures can save you a lot of time. The last thing anyone wants to do is dig through a ton of logs and code, just to find that the issue is an inverted comparison.

Overall, Testify is a great library for reducing the complexity of your Go test code. Every time I have used it, I have been extremely satisfied, as the functions do exactly what they say. I strongly suggest checking out the mock package and its use with mockery

The post Testify; Test Your Go Code appeared first on Module Safari.

]]>
https://modulesafari.com/testify-test-your-go-code/feed/ 0