testhelper

package
v2.3.0 Latest Latest
Warning

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

Go to latest
Published: Sep 14, 2023 License: MIT Imports: 17 Imported by: 2

Documentation

Overview

Package testhelper contains helper functions for doing things you might want to do in lots of your tests. For instance checking that an error value or a panic status was as expected.

The functions where a 't *testing.T' is passed will mark themselves as helpers by calling t.Helper(). These will also report the error themselves.

The testID parameter that many of the helpers take should be a string which can identify the instance of the test that is being run. For instance, if you have a slice of test cases you might want this string to include the index of the test and possibly some descriptive name. It might be created and used as follows:

testcases := []struct{
    name string
    ...
}{
    {
        name: "whatever",
    },
}
...
for i, tc := range testcases {
    tcID := fmt.Sprintf("test %d: %s", i, tc.name)
    ...
    testhelper.SomeFunc(t, tcID, ...)
    ...
}

Alternatively you can construct the testcase struct with an embedded testhelper.ID member. Then the testcase ID can be initialised with the MkID func. The ID string can then be created with the IDStr() func defined on the testhelper.ID as follows:

testcases := []struct{
    testhelper.ID
    ...
}{
    {
        ID: testhelper.MkID("whatever"),
    },
}
...
for _, tc := range testcases {
    tcID := tc.IDStr()
    ...
    testhelper.SomeFunc(t, tcID, ...)
    ...
}

This way of constructing a test case struct has the advantage that the constructed ID string gives more information about where the test was constructed and several of the testhelper functions take a testhelper.ID (or an interface which it satisfies).

The problem which the testhelper.ID solves is that reporting just the index number of a test (as in the previous example) imposes work on the tester such as counting tests to reach the failing test case. Using testhelper.ID gives you the filename and line number where the ID was instantiated.

There are additional mixin structs beside ID which allow you to record such things as whether an error is expected and if so what the error string should contain.

Index

Examples

Constants

View Source
const MaxReportedDiffs = 5

Variables

View Source
var OrigStdout = os.Stdout

Functions

func CheckAgainstGoldenFile deprecated

func CheckAgainstGoldenFile(t *testing.T, testID string, val []byte, gfName string, updGF bool) bool

CheckAgainstGoldenFile confirms that the value given matches the contents of the golden file and returns true if it does, false otherwise. It will report any errors it finds including any problems reading from or writing to the golden file itself. If the updGF flag is set to true then the golden file will be updated with the supplied value. You can set this value through a command-line parameter to the test and then pass that to this function as follows

var upd = flag.Bool("upd-gf", false, "update the golden files")
gfc := testhelper.GoldenFileCfg{
    DirNames: []string{"testdata"},
    Pfx:      "values",
    Sfx:      "txt",
}
...
testhelper.CheckAgainstGoldenFile(t,
    "my value test",
    val,
    gfc.PathName(t.Name()),
    *upd)

Then to update the golden files you would invoke the test command as follows

go test -upd-gf

Give the -v argument to go test to see what is being updated.

Deprecated: use the Check method on the GoldenFileCfg

func CheckError

func CheckError(t *testing.T, testID string, err error, expected bool, shouldContain []string) bool

CheckError checks that the error is nil if it is not expected, that it is non-nil if it is expected and that it contains the expected content if it is expected and non-nil. It will return false if there is any problem with the error, true otherwise

func CheckExpErr

func CheckExpErr(t *testing.T, err error, tce TestCaseWithErr) bool

CheckExpErr calls CheckError using the details from the test case to supply the parameters

func CheckExpErrWithID

func CheckExpErrWithID(t *testing.T, testID string, err error, te TestErr) bool

CheckExpErrWithID calls CheckError using the details from the TestErr to supply the parameters. The testID is supplied separately

func CheckExpPanic

func CheckExpPanic(
	t *testing.T, panicked bool, panicVal any,
	tp TestCaseWithPanic,
) bool

CheckExpPanic calls PanicCheckString using the details from the test case to supply the parameters

func CheckExpPanicError

func CheckExpPanicError(t *testing.T, panicked bool, panicVal any,
	tp TestCaseWithPanic,
) bool

CheckExpPanicError calls PanicCheckError using the details from the test case to supply the parameters

func CheckExpPanicErrorWithStack

func CheckExpPanicErrorWithStack(t *testing.T,
	panicked bool, panicVal any,
	tp TestCaseWithPanic, stackTrace []byte,
) bool

CheckExpPanicErrorWithStack calls PanicCheckErrorWithStack using the details from the test case to supply the parameters

func CheckExpPanicWithStack

func CheckExpPanicWithStack(
	t *testing.T, panicked bool, panicVal any,
	tp TestCaseWithPanic, stackTrace []byte,
) bool

CheckExpPanicWithStack calls PanicCheckStringWithStack using the details from the test case to supply the parameters

func DiffBool

func DiffBool(t *testing.T, id, name string, act, exp bool) bool

DiffBool compares the actual against the expected value and reports an error if they differ

func DiffErr

func DiffErr(t *testing.T, id, name string, act, exp error) bool

DiffErr compares the actual against the expected value and reports an error if they differ. Note that it compares the string representation and not the error type so there might be a mismatch.

func DiffFloat

func DiffFloat[T constraints.Float](t *testing.T, id, name string,
	act, exp, epsilon T,
) bool

DiffFloat compares the actual against the expected value and reports an error if they differ by more than epsilon

func DiffFloatSlice

func DiffFloatSlice[F constraints.Float](t *testing.T, id, name string,
	act, exp []F, epsilon F,
) bool

DiffFloat64Slice compares the actual against the expected value and reports an error if they differ. At most MaxReportedDiffs are reported.

func DiffInt

func DiffInt[T constraints.Integer](t *testing.T, id, name string,
	act, exp T,
) bool

DiffInt compares the actual against the expected value and reports an error if they differ

func DiffSlice

func DiffSlice[C comparable](t *testing.T, id, name string, act, exp []C) bool

DiffSlice compares the actual against the expected value and reports an error if they differ. At most MaxReportedDiffs are reported.

func DiffString

func DiffString[S ~string](t *testing.T, id, name string, act, exp S) bool

DiffString compares the actual against the expected value and reports an error if they differ

func DiffStringSlice

func DiffStringSlice[S ~string](t *testing.T, id, name string,
	act, exp []S,
) bool

DiffStringSlice compares the actual against the expected value and reports an error if they differ. At most MaxReportedDiffs are reported.

func DiffStringer

func DiffStringer(t *testing.T, id, name string, actS, expS fmt.Stringer) bool

DiffStringer compares the actual against the expected value and reports an error if they differ. It will report them as different if one is nil or has a nil value and the other isn't/doesn't or if they are both non-nil and the string values differ.

func DiffTime

func DiffTime(t *testing.T, id, name string, act, exp time.Time) bool

DiffTime compares the actual against the expected value and reports an error if they differ

func DiffVals

func DiffVals(actVal, expVal any, ignore ...[]string) error

DiffVals compares the actual and expected values and returns an error if they are different. This differs from the reflect package function DeepEqual in that the error shows which fields are different.

The ignore argument holds the names of fields to not compare, the slice represents a chain of names in nested structs. So, for instance an ignore value containing the pair of values ["a", "b"] means to not compare the field called "b" in the sub-struct called "a".

func ErrSliceToStrSlice

func ErrSliceToStrSlice(es []error) []string

ErrSliceToStrSlice creates a string slice from the supplied slice of errors - each string is the result of the Error func being called on the corresponding error

func MakeTempDir

func MakeTempDir(name string, perms os.FileMode) func()

MakeTempDir creates a directory with the given name and permissions and returns a function that will delete the directory. This can be used as a defer func to tidy up after the tests are complete. This might be useful where you want to create an empty or unreadable directory for a test.

It will panic if the directory cannot be created or the permissions set.

Only the Permission bits of the perms are used, any other values are masked out before use.

func MakeTempDirCopy added in v2.3.0

func MakeTempDirCopy(fromDir string) (string, func() error, error)

MakeTempDirCopy creates a temporary directory and copies the contents of fromDir into it. It returns the name of the temporary directory, a function to clean up (remove the temp dir) and any error encountered. Only regular files and sub-directories can be copied. Any other types of file in the from-directory will cause an error.

func PanicCheckError

func PanicCheckError(t *testing.T, testID string,
	panicked, panicExpected bool,
	panicVal any, shouldContain []string,
) bool

PanicCheckError tests the panic value (which should be an error) against the passed values. It will report an error if the panic status is unexpected.

func PanicCheckErrorWithStack

func PanicCheckErrorWithStack(t *testing.T, testID string,
	panicked, panicExpected bool,
	panicVal any, shouldContain []string, stackTrace []byte,
) bool

PanicCheckErrorWithStack tests the panic value (which should be an error) against the passed values. A stack trace should also be passed which will be printed if the panic is not as expected and a panic was seen

func PanicCheckString

func PanicCheckString(t *testing.T, testID string,
	panicked, panicExpected bool,
	panicVal any, shouldContain []string,
) bool

PanicCheckString tests the panic value (which should be a string) against the passed values. It will report an error if the panic status is unexpected.

func PanicCheckStringWithStack

func PanicCheckStringWithStack(t *testing.T, testID string,
	panicked, panicExpected bool,
	panicVal any, shouldContain []string, stackTrace []byte,
) bool

PanicCheckStringWithStack tests the panic value (which should be a string) against the passed values. A stack trace should also be passed which will be printed if the panic is not as expected and a panic was seen

func PanicSafe

func PanicSafe(f func()) (panicked bool, panicVal any)

PanicSafe will call the passed func, check if it has panicked and return true and the recovered panic value if it has and false and nil otherwise. You can call it passing a closure that does the work you are testing or any func matching the signature. It is intended to reduce the volume of boilerplate code you need.

Example

ExamplePanicSafe demonstrates how the PanicSafe function may be used

package main

import (
	"fmt"

	"github.com/nickwells/testhelper.mod/v2/testhelper"
)

func main() {
	panicked, panicVal := testhelper.PanicSafe(func() { panic("As expected") })
	fmt.Println("Panicked:", panicked)
	fmt.Println("PanicVal:", panicVal)
}
Output:

Panicked: true
PanicVal: As expected

func ReportUnexpectedPanic

func ReportUnexpectedPanic(t *testing.T, testID string,
	panicked bool, panicVal any, stackTrace []byte,
) bool

ReportUnexpectedPanic will check if panicked is true and will report the unexpected panic if true. it returns the panicked value

func ShouldContain

func ShouldContain(t *testing.T, testID, desc, act string, exp []string) bool

ShouldContain checks that the string 'act' contains all of the strings in the 'exp' argument and reports an error if it does not. The desc parameter is used to describe the string being checked. It returns true if a problem was found, false otherwise.

func StringSliceDiff

func StringSliceDiff(a, b []string) bool

StringSliceDiff will compare two slices of strings for equality. If they are of different lengths they are taken to be different. A nil slice and an empty slice are taken to be the same.

func TempChmod

func TempChmod(name string, perms os.FileMode) func()

TempChmod will change the FileMode of the named file and return a function that will restore the original perms.

It will panic if the permission bits can't be set or the original values obtained.

Only the Permission bits of the perms are used, any other values are masked out before use.

Types

type DiffValErr

type DiffValErr struct {
	// contains filtered or unexported fields
}

DiffValErr holds an error reflecting the difference between two interface values. It is the type of error returned by the DiffVals func

func (DiffValErr) Error

func (dve DiffValErr) Error() string

Error returns a string expressing the DiffValErr

type EnvCache added in v2.1.0

type EnvCache struct {
	Stack []EnvEntry
}

EnvCache maintains a stack of EnvEntry's. It records the previous value of each environment variable which has been set by the Setenv method. This allows the values to be reset to their original values by the ResetEnv method.

func (*EnvCache) ResetEnv added in v2.1.0

func (ec *EnvCache) ResetEnv()

ResetEnv resets the environment to its state prior to the modifications made through the use of the Setenv method. It clears the stack after the environment has been restored. Note that the environment is not restored exactly as it was; variables which didn't previously exist at all will afterwards exist but with an empty value.

func (*EnvCache) Setenv added in v2.1.0

func (ec *EnvCache) Setenv(entries ...EnvEntry) error

Setenv sets the environment values given by the EnvEntry parameters. It records the prior value so that it can be reset later using the ResetEnv method. The first failure to set a value returns the error and subsequent values are not set.

type EnvEntry added in v2.1.0

type EnvEntry struct {
	Key   string
	Value string
}

EnvEntry records the name and value of an environment variable

type ExpErr

type ExpErr struct {
	Expected         bool
	ErrShouldContain []string
}

ExpErr records common details about error expectations for a test case. It is intended that this should be embedded in a test case structure, which will also have an ID structure embedded. The resulting test case can then be passed to the CheckExpErr func. It is similar to the ExpPanic structure in form and intended use.

Example
package main

import (
	"github.com/nickwells/testhelper.mod/v2/testhelper"
)

func main() {
	testCases := []struct {
		testhelper.ID
		testhelper.ExpErr
		v int
	}{
		{
			ID: testhelper.MkID("No error expected"),
			v:  1,
		},
		{
			ID:     testhelper.MkID("error expected"),
			ExpErr: testhelper.MkExpErr("part1", "another part"),
			v:      99,
		},
	}

	_ = testCases // this is just to ensure that the testCases var is used
	// for _, tc := range testCases {
	//
	// we can then run the test, collect any error returned and check
	// that it is as expected. With the ExpErr member left at its default
	// value no error is expecteed. If it has a value the error is
	// expected and it should contain all the supplied strings.
	//
	// With the testcase structure having an ID and an ExpErr you can
	// call
	//
	//     testhelper.CheckExpErr(t, err, tc)
	//
	// just passing the testing.T pointer (t), the error (err) and the
	// testcase (tc). This will report any missing or unexpected errors
	// or errors that have unexpected values. It wil return false if
	// there are any problems
	// }
}
Output:

func MkExpErr

func MkExpErr(s ...string) ExpErr

MkExpErr is a constructor for the ExpErr struct. The Expected flag is always set to true and the slice of strings that the error should contain is set to the slice of strings passed. For an ExpErr where the error is not expected just leave it in its default state.

func (ExpErr) ErrExpected

func (e ExpErr) ErrExpected() bool

ErrExpected returns true or false according to the value of the Expected field

func (ExpErr) ErrShldCont

func (e ExpErr) ErrShldCont() []string

ErrShldCont returns the value of the ShouldContain field

type ExpPanic

type ExpPanic struct {
	Expected      bool
	ShouldContain []string
}

ExpPanic records common details about panic expectations for a test case. It is intended that this should be embedded in a test case structure, which will also have an ID structure embedded. The resulting test case can then be passed to the CheckExpPanic func. It is similar to the ExpErr structure in form and intended use.

Note that this expects any panic value to be a string and it will report an error if that is not the case.

Example
package main

import (
	"github.com/nickwells/testhelper.mod/v2/testhelper"
)

func main() {
	testCases := []struct {
		testhelper.ID
		testhelper.ExpPanic
		v int
	}{
		{
			ID: testhelper.MkID("No panic expected"),
			v:  1,
		},
		{
			ID:       testhelper.MkID("panic expected"),
			ExpPanic: testhelper.MkExpPanic("part1", "another part"),
			v:        2,
		},
	}

	_ = testCases // this is just to ensure that the testCases var is used
	// for _, tc := range testCases {
	// Use the testhelper.PanicSafe func
	//
	// This func calls the code to be tested and recovers from any panics.
	// It returns true if a panic was seen and an interface value
	// containing the panic value. We then collect these returned values
	// and check that they are as expected. With the ExpPanic member left
	// at its default value no panic is expecteed. If it has a value the
	// panic is expected and it should contain all the supplied strings.
	//
	// With the testcase structure having an ID and an ExpPanic you can
	// call
	//
	//     testhelper.CheckExpPanic(t, panicked, panicVal, tc)
	//
	// just passing the testing.T pointer (t), the boolean indicating
	// whether a panic was seen , the panic value and the testcase
	// (tc). This will report any missing or unexpected panics or panics
	// that have unexpected values. It wil return true if there are any
	// problems. There is an alternative function that can be called
	// which allows you to pass a stack trace as a final parameter; this
	// is useful if you get an unexpected panic and want to find out
	// where it came from
	// }
}
Output:

func MkExpPanic

func MkExpPanic(s ...string) ExpPanic

MkExpPanic is a constructor for the ExpPanic struct. The Expected flag is always set to true and the slice of strings that the panic should contain is set to the slice of strings passed. For an ExpPanic where a panic is not expected just leave it in its default state.

func (ExpPanic) PanicExpected

func (p ExpPanic) PanicExpected() bool

PanicExpected returns true or false according to the value of the PanicExpected field

func (ExpPanic) PanicShldCont

func (p ExpPanic) PanicShldCont() []string

PanicShldCont returns the value of the ShouldContain field

type FakeIO added in v2.2.0

type FakeIO struct {
	sync.Mutex
	// contains filtered or unexported fields
}

FakeIO holds the details needed to redirect and restore Stdin, Stdout and Stderr for the duration of a test. Create this just before the test with a NewStdio... func and after the test is complete restore the original settings by calling the Done method which will also return the contents of anything written to stdout and stderr.

func NewStdioFromString added in v2.2.0

func NewStdioFromString(input string) (fio *FakeIO, err error)

NewStdioFromString will create a Stdio object which will provide access to the contents of anything written to stdout or stderr. After this has been called any code reading from stdin will get the contents of the passed string. Any output to stdout or stderr will be captured

func (*FakeIO) Done added in v2.2.0

func (fio *FakeIO) Done() (stdout, stderr []byte, err error)

Done tidies up, restores the std IO values to their previous settings and returns anything written to stdout and stderr. It is an error to call this twice on the same FakeIO.

type GoldenFileCfg

type GoldenFileCfg struct {
	DirNames []string
	Pfx      string
	Sfx      string

	UpdFlagName string

	KeepBadResultsFlagName string
	// contains filtered or unexported fields
}

GoldenFileCfg holds common configuration details for a collection of golden files. It helps with consistent naming of golden files without having to repeat common parts throughout the code.

A golden file is a file that holds expected output (typically lengthy) that can be compared as part of a test. It avoids the need to have a long string in the body of a test.

DirNames is a slice of strings holding the parts of the directory path
to the file

Pfx is an optional prefix - leave it as an empty string to exclude it

Sfx is an optional suffix - as for the prefix

UpdFlagName is the name of a flag that will set a bool used to decide
whether or not to update the golden file. If it is not set then it is
ignored. If you have set this then you should also call the AddUpdateFlag
method (typically in an init() function) and then use the Check method
to compare with the file

KeepBadResultsFlagName is the name of a flag that will set a bool used
to decide whether or not to keep bad results. If it is not set then it
is ignored. If you have set this then you should also call the
AddKeepBadResultsFlag method (typically in an init() function) and then
use the Check method to compare with the file

func (*GoldenFileCfg) AddKeepBadResultsFlag

func (gfc *GoldenFileCfg) AddKeepBadResultsFlag()

AddKeepBadResultsFlag adds a new flag to the standard flag package. The flag is used to control whether or not to keep the bad results in a file. The name of the file will be the name of the Golden file with ".badResults" as a suffix. These files can then be compared with the Golden files do see what changes have been made . This can then be set on the command line when testing and looked up by the GoldenFileCfg.Check method. The Check method will report the flag name to use if any is available.

func (*GoldenFileCfg) AddUpdateFlag

func (gfc *GoldenFileCfg) AddUpdateFlag()

AddUpdateFlag adds a new flag to the standard flag package. The flag is used to control whether or not to update the Golden files with the new values rather than reporting differences as test errors. If there is already a Golden file present then this will be preserved in a file with the same name as the Golden file but with ".orig" as a suffix. This can then be set on the command line when testing and looked up by the GoldenFileCfg.Check method. The Check method will report the flag name to use if any is available.

func (GoldenFileCfg) Check

func (gfc GoldenFileCfg) Check(t *testing.T, id, gfName string, val []byte) bool

Check confirms that the value given matches the contents of the golden file and returns true if it does, false otherwise. It will report any errors it finds including any problems reading from or writing to the golden file itself.

If UpdFlagName is not empty and the AddUpdateFlag method has been called (typically in an init() function) then the corresponding flag value will be looked up and if the flag is set to true the golden file will be updated with the supplied value. You can set this value through a command-line parameter to the test and then pass that to this function as follows:

gfc := testhelper.GoldenFileCfg{
    DirNames:    []string{"testdata"},
    Pfx:         "values",
    Sfx:         "txt",
    UpdFlagName: "upd-gf",
}

func init() {
    gfc.AddUpdateFlag()
}
...
gfc.Check(t, "my value test", t.Name(), val)

Then to update the golden files you would invoke the test command as follows:

go test -upd-gf

Similarly with the KeepBadResultsFlag.

Give the -v argument to go test to see what is being updated.

An advantage of using this method (over using the testhelper.CheckAgainstGoldenFile function) is that this will show the name of the flag to use in order to update the files. You save the hassle of scanning the code to find out what you called the flag.

func (GoldenFileCfg) PathName

func (gfc GoldenFileCfg) PathName(name string) string

PathName will return the name of a golden file. It applies the directory names and any prefix or suffix to the supplied string to give a well-formed name using the appropriate filepath separators for the operating system. A suggested name to pass to this method might be the name of the current test as given by the Name() method on testing.T.

Note that any supplied name is "cleaned" by removing any part prior to an embedded filepath.Separator.

type ID

type ID struct {
	Name       string
	At         string
	AtFullName string
}

ID holds common identifying information about a test. Several of the testhelper functions take an ID (or an interface which it satisfies) which can simplify the tests. This is often combined with other testhelper mixin structs to record standard details for common tests.

func MkID

func MkID(name string) ID

MkID is a constructor for the ID type. It will record where it was called from and the resulting ID, when used to report an error, will give information on the location which should speed up locating the test setup for the failing test.

func (ID) IDStr

func (id ID) IDStr() string

IDStr returns an identifying string describing the test

Example
package main

import (
	"fmt"

	"github.com/nickwells/testhelper.mod/v2/testhelper"
)

func main() {
	testCases := []struct {
		testhelper.ID
		v1, v2 int
	}{
		{
			ID: testhelper.MkID("my first example"),
			v1: 1,
			v2: 2,
		},
	}

	for _, tc := range testCases {
		if tc.v1 != tc.v2 { // You could use the testhelper.DiffInt(...) func
			fmt.Println(tc.IDStr()) // in a real test this will be t.Error(...)
		}
	}
}
Output:

test: examples_test.go:15: my first example

func (ID) IDStrFullName

func (id ID) IDStrFullName() string

IDStrFullName returns an identifying string describing the test, using the full pathname of the file where the MkID func was called rather than just the last part of the path. You might want to use this if your test cases are initialised in a more complex way and there is some ambiguity as to the location of a source file.

type TestCase

type TestCase interface {
	IDStr() string
	IDStrFullName() string
}

TestCase is an interface wrapping the IDStr methods

type TestCaseWithErr

type TestCaseWithErr interface {
	TestCase
	TestErr
}

TestCaseWithErr combines the TestCase and TestErr interfaces

type TestCaseWithPanic

type TestCaseWithPanic interface {
	TestCase
	TestPanic
}

TestCaseWithPanic combines the TestCase and TestPanic interfaces

type TestErr

type TestErr interface {
	ErrExpected() bool
	ErrShldCont() []string
}

TestErr is an interface wrapping the error expectation methods

type TestPanic

type TestPanic interface {
	PanicExpected() bool
	PanicShldCont() []string
}

TestPanic is an interface wrapping the panic expectation methods

Jump to

Keyboard shortcuts

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