testifyRecursive

package module
v0.2.1-beta Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 23, 2021 License: MIT Imports: 3 Imported by: 0

README

goTestifyRecursive

Extending https://github.com/stretchr/testify.git with recursive capabilities.

Tested for go 1.15.

Build Status codecov PkgGoDev

Why

The problem

If you test your GO code (and you should), you're probably writting a lot of assert functions in order to test every fields of some struct.

package main

import (
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestMyFunction(t *testing.T) {
    res := MyFunction()
    
    assert.Equal(t, "foo", res.A)
    assert.Equal(t, "bar", res.B)
    // ^ And potentially many more asserts here
}

While this is a perfectly useful test, here is one problem : if your struct MyStruct changes in a next release, you may forget to update your test TestMyFunction. When you're adding a new field, forgetting to test its value in your existing tests won't prevent them to pass.

Solution provided

Enters goTestifyRecursive, which introduces the AssertRecursive function. Here is how the test above is rewritten :

package main

import (
	rec "github.com/MaximeWeyl/goTestifyRecursive"
	bh "github.com/MaximeWeyl/goTestifyRecursive/behaviours"
	"testing"
)

func TestMyFunction(t *testing.T) {
    res := MyFunction()
    rec.AssertRecursive(t, res, bh.ExpectedStruct{
    	"A": "foo",
    	"B": "bar",
    })
}

If, later, you add a new Field to your struct, the test will fail, letting you know which field is missing :

Error:      	Fields are missing
Test:       	TestMyFunction
Messages:   	Some fields are missing : {C}

But you said "recursive"

Yes and here is why : This new syntax allow you to write nested assertions, just like this :

package main

import (
	rec "github.com/MaximeWeyl/goTestifyRecursive"
	bh "github.com/MaximeWeyl/goTestifyRecursive/behaviours"
	"testing"
)

type MyStruct struct {
	A string
	B []MyStruct
}

func TestMyFunction(t *testing.T) {
    res := MyFunction()
    rec.AssertRecursive(t, res, bh.ExpectedStruct{
    	"A": "foo",
    	"B": bh.ExpectedSlice{
    		bh.ExpectedStruct{"A":"bar1", "B": []MyStruct(nil)},
    		bh.ExpectedStruct{"A":"bar2", "B": bh.ExpectedSlice{}},
        },
    })
}

The errors will also try to show you were your error is, taking into account nested slices and structs :

Error:      	Field not found
Test:       	TestMyFunction
Messages:   	Expected field not found : {B[1].C}

This way of writing tests prevents you from forgetting to update tests that need to be.

But I do not want to check for this new field

No problem, the idea of this lib is to let you know that you may want to check for a new field. But if you decided that this new field should not be tested in this test, you can tell the lib to ignore it : you're the one that know your code best after all. Just use the IgnoredField struct.

package main

import (
	rec "github.com/MaximeWeyl/goTestifyRecursive"
	bh "github.com/MaximeWeyl/goTestifyRecursive/behaviours"
	"testing"
)

func TestMyFunction(t *testing.T) {
    res := MyFunction()
    rec.AssertRecursive(t, res, bh.ExpectedStruct{
    	"A": "foo",
    	"B": "bar",
    	"NewField": bh.IgnoredField{},
    })
}

Behaviours

In the previous sections we have seen several behaviours. The first one is known as the default behaviour : It is what happen when you pass an argument that does not implement the special interface FieldBehaviour.

In this case, the assertion checks that the field actual value is Equal to the expected value, with the help of testify's Equal function.

If you use an expected value that implements the FieldBehaviour interface, you will get customized behaviours, like with ExpectedStruct or ExpectedSlice or IgnoredField. This lib let you use several other behaviours, in the package github.com/MaximeWeyl/goTestifyRecursive/behaviours.

Writing your own behaviours

Writing a new behaviour is as simple as implementing the FieldBehaviour interface.

type FieldBehaviour interface {
CheckField(t assert.TestingT, actualValueInterface interface{}, parentFieldName string) bool
}

For instance, here is the code to write a behaviour that asserts that the field is not empty, using the original testify lib (this example is officially in this lib's behaviours).

package behaviours

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

type NotEmptyField struct {
}

func (n NotEmptyField) CheckField(t assert.TestingT, actualValueInterface interface{}, fieldName string) bool {
	return assert.NotEmptyf(t, actualValueInterface, "Field {%s} should not be empty, but was", fieldName)
}

Using any testify function as behaviour

You can use any function from testify package (or any other function as long as it uses the same conventions).

This function must begin with a "t" parameter and return a bool parameter (like any functions in the testify's assert package)

For this, you need to use the Func behaviour factory, which takes the function (for instance, assert.Equal) as first parameter.

Any following parameter will be passed to the function. The first "t" parameter should never be passed, the one from AssertRecursive or RequireRecursive will be used.

In order to pass the currently tested value, use the custom parameter X. In order to pass the name of the current field, use the custom parameter F (this is useful for printing custom error messages).

package main

import (
	rec "github.com/MaximeWeyl/goTestifyRecursive"
	bh "github.com/MaximeWeyl/goTestifyRecursive/behaviours"
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestName(t *testing.T) {
	a := struct {
		A int
	}{
		A: 12,
	}

	expected := 12
	rec.AssertRecursive(t, a, bh.ExpectedStruct{
		"A": bh.Func(assert.Equal, expected, bh.X, "Error for field %s : expected %d but got %d", bh.F, expected, bh.X),
	})
}

Require instead of Assert

Like in testify, you can stop your test immediately if the assertion fails. Simply use the function RequireRecursive instead of AssertRecursive

Contributing

Contributions are welcome, especially for writing new behaviours.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AssertRecursive

func AssertRecursive(t assert.TestingT, actual interface{}, expected behaviours.FieldBehaviour) bool

AssertRecursive Performs a recursive assertion between an expected behaviour, and an actual value

func RequireRecursive

func RequireRecursive(t require.TestingT, actual interface{}, expected behaviours.FieldBehaviour)

RequireRecursive Same as AssertRecursive, but stops the test if failed

Types

This section is empty.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL