Documentation
¶
Overview ¶
Package testaroli allows to monkey patch Go test binary, e.g. override functions and methods with stubs/mocks to simplify unit testing. It should be used only for unit testing and never in production!
Platforms suported ¶
This package modifies actual executable at runtime, therefore is OS- and CPU arch-specific.
Supported OSes:
- Linux
- macOS
- Windows
- FreeBSD (other BSD flavours should also be ok)
Supported CPU archs:
- x86-64
- ARM64 aka Aarch64
The concept ¶
This package allows you to modify test binary to call your mocks instead of functions it supposed to call. It internally maintains the override chain, it means it is possible not to override all needed functions with mocks at the start, but one by one, thus controlling the order of calls. To achieve this, when overriding function with mock it is required to specify the number of calls this mock should be called, and when this counter is reached, the overridden function gets reset to its original state i.e. no longer overridden, and next function in the chain gets overridden.
However, there are to special counter values - Unlimited and Always. Override with Unlimited count is not removed from chain until Reset/ResetAll function is called, it means no overrides behind Unlimited one become effective until Unlimited override is reset.
Always override, unlike any other overrides, is always effective, it means if doesn't belong to the chain. If it is not important to control correct order of mock calls, the test case can use only Always overrides. However, there is a limitation - Always override for the function X is mutually exclusive with any other override for the same function X, so attempt to Always override previously overridden function will panic. Similarly to all other overrides, Always override can be reset with Reset/ResetAll.
Command line options ¶
It is recommended to switch off compiler optimisations and disable function inlining using `-gcflags="all=-N -l"` CLI option when running tests, like this:
go test -gcflags="all=-N -l" [<path>]
Typical use:
// you want to test function foo() which in turn calls function bar(), so you // override function bar() to check whether it is called with correct argument // and to return predefined result func foo() error { ... if err := bar(baz); err != nil { return err } ... } func bar(baz int) error { ... } func TestBarFailing(t *testing.T) { Override(TestingContext(t), bar, Once, func(a int) error { Expectation().CheckArgs(a) // <-- actual arg 'a' value compared with expected value 42 return ErrInvalid })(42) // <-- expected argument value err := foo() if !errors.Is(err, ErrInvalid) { t.Errorf("unexpected %v", err) } if err = ExpectationsWereMet(); err != nil { t.Error(err) } }
It is also possible to override functions and methods in other packages, including ones from standard library, like in example below. Please note that method receiver becomes the first argument of the mock function.
func TestFoo(t *testing.T) { Override(TestingContext(t), (*os.File).Read, Once, func(f *os.File, b []byte) (n int, err error) { Expectation() copy(b, []byte("foo")) return 3, nil }) f, _ := os.Open("test.file") defer f.Close() buf := make([]byte, 3) n, _ := f.Read(buf) if n != 3 || string(buf) != "foo" { t.Errorf("unexpected file content %s", string(buf)) } if err = ExpectationsWereMet(); err != nil { t.Error(err) } }
Index ¶
Constants ¶
const ( Once = 1 Unlimited = -1 Always = -2 )
Variables ¶
var ErrExpectationsNotMet = errors.New("expectaions were not met")
Functions ¶
func ExpectationsWereMet ¶
func ExpectationsWereMet() error
ExpectationsWereMet checks that all overridden functions were called, as expected. It doesn't check correct order of functions called (it is responsibility of Expectation) and it doesn't check function arguments (it is responsibility of Expect.CheckArgs). It is important to call ExpectationsWereMet at the end of test case to restore original state of overridden functions.
func Override ¶
Override overrides <org> with <mock>. The signatures of <org> and <mock> must match exactly, otherwise compilation error is reported. It has <count> argument to specify how many calls to <org> functions are expected, which must be a positive number, Unlimited or Always. If this is the first non-Always override, the function get overridden immediately, all subsequent non-Always overrides are put into override chain. After <org> function got called <count> times, the <org> function is no longer overridden and next override in the chain becomes effective.
Unlimited value for <count> means that there is no limit for number of <org> calls. Override following Unlimited one in he chain becomes effective only after Unlimited override is reset with Reset/ResetAll.
Always value for count means that override is not a part of override chain and and always effective (until reset). It is an error to Always override previously overridden function or override the function that was previously overridden with Always, in either case Override panics.
It is ok to call Override several times, however only the first override becomes immediately effecive, all subsequent overrides are placed in the chain and become effective only when previous override is completed. It means that order of overrides must match the order of called functions exactly. For example:
// Function bar() will be overridden only after override for foo() is processed Override(ctx, foo, Once, func (a int, b string) { Expectation().CheckArgs(a, b) })(42, "qwerty") Override(ctx, bar, Once, func (a int) { Expectation().CheckArgs(a) })(1024) // foo() is overridden, bar() is not bar(512) // <--- call to original bar() foo(42, "qwerty") // <--- call to overridden version of foo() // bar() is overridden, foo() is not bar(1024) // <--- call to overridden version of bar() // nothing is overridden
Override returns function of generic type T that allows to set expected values for function call, like this:
Override(ctx, foo, Once, func (a int, b string) { Expectation().CheckArgs(a, b) })(42, "bar")
and it works the same as
Override(ctx, foo, Once, func (a int, b string) { Expectation().Expect(42, "bar").CheckArgs(a, b) })
but has a benefit of checking types for expected values at compile time, thanks to Go generics.
Override takes a context as a first argument, and this context must be created with TestingContext or derived from the context, returned by TestingContext. This function panics if is given invalid context.
It is important to remember that mock function is executed in the scope of original function, therefore variables and functions, declared within test case scope, are not accessible, although compiler considers the code valid. To overcome this limitation pass the variable in the context, in this case you can obtain the variable from the context from within mock function, for example:
ctx := context.WithValue(TestingContext(t), key, 100) Override(ctx, bar, Once, func() { // using 'val' here leads to runtime errors val := Expectation().Context().Value(key).(int) // now 'val' is ok to use and contains the value 100 })
You can override regular functions and methods, including standard ones, but not the interface methods.
func Reset ¶
func Reset(org any)
Reset resets previously overridden function. If there are several overrides for the function in the override chain, only the first match gets reset. Use ResetAll to reset all matching entries. If the function being reset is currently overridden with non-Always count (first in override chain), next function in override chain gets overridden.
Resetting not overridden function is a safe no-op.
func ResetAll ¶
func ResetAll(org any)
ResetAll resets previously overridden function. If there are several overrides for the function in the override chain, all of them get reset/removed for the chain. To remove only the first match use Reset. If the function being reset is currently overridden with non-Always count (first in override chain), next function in override chain gets overridden.
Resetting not overridden function is a safe no-op.
func Testing ¶
Testing returns the testing.T, embedded into the context with TestingContext. If the context wasn't created with TestingContext, this function panics.
Types ¶
type Expect ¶
type Expect struct {
// contains filtered or unexported fields
}
Expect holds information about overridden function and has methods to set and check arguments.
func Expectation ¶
func Expectation() *Expect
Expectation can be called only from inside the mock (it panics otherwise), it checks whether function call was expected at this point, and return matching expectation.
It is important to always call Expectation from the mock function, even if you don't want to check arguments, because Expectation checks that function was called in order, and if it was the last expected call for overridden function, it restores the original state and overrides next function in the chain.
func (Expect) CheckArgs ¶
CheckArgs checks if actual values match the expected ones.
Please note that when reporting differences, this function always use zero-based numbering - for array/slice elements, function arguments and run numbers, e.g. first call (if function was overridden for several calls) is called `run 0`
func (Expect) Context ¶
Context returns context.Context, passed to Override function.
func (*Expect) Expect ¶
Expect sets the expected argument values, that can be later checked with Expect.CheckArgs. See Override for better way (with compile-time type checks) of setting expected values.
func (Expect) RunNumber ¶
RunNumber returns the sequence number of current run for the override. Count is zero-based, so for the first run it returns 0.
It is useful when you need your mock to behave differently on different runs, for example:
// v-- function to be called twice Override(ctx, foo, 2, func (a int, b string) { e := Expectation() if e.RunNumber() == 0 { // zero-based, so first run is number 0 e = e.Expect(42, "foo") } else { e = e.Expect(42, "bar") } e.CheckArgs(a, b) })