pers

package
v0.6.6 Latest Latest
Warning

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

Go to latest
Published: Feb 12, 2024 License: Unlicense Imports: 15 Imported by: 0

Documentation

Overview

Package pers (hel...pers ... get it? Yeah, I know, I have a love/hate relationship with myself too) contains a bunch of helpers for working with hel mocks. From making a mock consistently return to matchers - they'll all be here.

Index

Examples

Constants

View Source
const Any argAny = -1

Any is a special value to tell pers to ignore the value at the position used. See the various options for how this value may be used.

As an example, you can assert only on the second argument with:

HaveMethodExecuted("Foo", WithArgs(Any, 22))
View Source
const VariadicAny variadicAny = -2

VariadicAny is a special value, similar to Any, but specifically to tell pers to allow any number of values for the variadic arguments. It must be passed in after all non-variadic arguments so that its position matches the position that variadic arguments would normally be.

This cannot be used to check some variadic arguments without checking the others - at least right now, you must either assert on all of the variadic arguments or none of them.

Variables

This section is empty.

Functions

func BeCalled added in v0.6.6

func BeCalled[T *U, U any](methodName string, opts ...MethodAssertOption) func(mock T) it.Result

BeCalled is a match function for use with the git.sr.ht/~nelsam/correct family of assertion libraries. It may also be used standalone to check if a method was called without explicitly failing the test.

The type should be a pointer to a hel mock.

func BlockReturns added in v0.5.0

func BlockReturns(t T, mock any, methodName string) (unblock func())

BlockReturns blocks a mock method from returning until the returned unblock function is called. This may be used to pause ConsistentlyReturn or just to prevent a method from returning before a test has finished checking the application state.

func ConsistentlyReturn

func ConsistentlyReturn(t T, mock any, args ...any) (stop func())

ConsistentlyReturn will continue adding a given value to the channel until the test is done or the returned stop function is called, whichever happens first. When ConsistentlyReturn stops adding values to the channel(s), it will drain those channels before returning.

Mock method calls may be temporarily paused using BlockReturns, which prevents ConsistentlyReturn from returning until unblocked.

After the first call to stop (or after the test completes), calls to stop will be a no-op.

The value for mock may be either a channel or a struct full of channels (usually a return struct from a generated mock).

ConsistentlyReturn will panic if:

  • args contains a different number of arguments than the number of channels on mock.
  • any of the arguments passed in are not compatible to the return types of mock.

func Expect added in v0.6.6

func Expect[T *U, U any](seq *Sequence, mock T, methodName string, opts ...MethodAssertOption)

Expect adds an expected method call to the sequence.

Options intended for applying assertions against concurrent code (like Within) are ignored.

If no Returning option is used, Expect will automatically return the zero value for each return type on the method.

func Go added in v0.6.5

func Go[T any](t TestingT, f func() (T, error)) (<-chan T, <-chan error)

Go helps perform concurrent testing. In some cases, it's useful for a test to take complete control over the flow of business logic - for example, to test how the code handles timeouts or to perform checks before allowing logic to resume.

In these cases, it's common to kick off business logic in a separate goroutine. The business logic will block each time it calls a method on a hel mock, which allows us to perform assertions while the business logic is paused, then tell it to resume by telling the mock to return.

Go is provided mainly to document this pattern; but also it helps make some of the setup simpler.

See the examples for detail.

Example
package main

import (
	"fmt"
	"time"

	"git.sr.ht/~nelsam/hel/pkg/pers"
	"git.sr.ht/~nelsam/hel/vegr"
	"git.sr.ht/~nelsam/hel/vegr/ret"
)

type mockSomeType struct {
	t                vegr.TestingT
	timeout          time.Duration
	SomeMethodCalled chan bool
	SomeMethodInput  struct {
		Arg0 chan string
		Arg1 chan string
	}
	SomeMethodOutput struct {
		MockReturnBlocker ret.Blocker
		MockPanicker      ret.Panicker
		Ret0              chan string
	}
	SomeOtherMethodCalled chan bool
	SomeOtherMethodOutput struct {
		MockReturnBlocker ret.Blocker
		MockPanicker      ret.Panicker
	}
}

func newMockSomeType(t vegr.TestingT, timeout time.Duration) *mockSomeType {
	m := &mockSomeType{t: t, timeout: timeout}
	m.SomeMethodCalled = make(chan bool, 100)
	m.SomeMethodInput.Arg0 = make(chan string, 100)
	m.SomeMethodInput.Arg1 = make(chan string, 100)
	m.SomeMethodOutput.MockReturnBlocker = vegr.BlockChan()
	m.SomeMethodOutput.MockPanicker = make(ret.Panicker, 100)
	m.SomeMethodOutput.Ret0 = make(chan string, 100)
	m.SomeOtherMethodCalled = make(chan bool, 100)
	m.SomeOtherMethodOutput.MockReturnBlocker = vegr.BlockChan()
	m.SomeOtherMethodOutput.MockPanicker = make(ret.Panicker, 100)
	return m
}
func (m *mockSomeType) SomeMethod(arg0 string, arg1 string) (ret0 string) {
	m.t.Helper()
	m.SomeMethodCalled <- true
	m.SomeMethodInput.Arg0 <- arg0
	m.SomeMethodInput.Arg1 <- arg1
	vegr.PopulateReturns(m.t, "SomeMethod", m.timeout, m.SomeMethodOutput, &ret0)
	return ret0
}
func (m *mockSomeType) SomeOtherMethod() {
	m.t.Helper()
	m.SomeOtherMethodCalled <- true
	vegr.PopulateReturns(m.t, "SomeOtherMethod", m.timeout, m.SomeOtherMethodOutput)
}

type printingT struct{}

func (t printingT) Fatalf(s string, args ...any) {
	fmt.Printf(s+"\n", args...)
}

func (t printingT) Logf(s string, args ...any) {
	fmt.Printf(s+"\n", args...)
}

func (t printingT) Failed() bool {
	return false
}

func (t printingT) Helper() {
}

func (t printingT) Cleanup(func()) {
}

type SomeType interface {
	SomeMethod(string, string) string
	SomeOtherMethod()
}

func someFunc(v SomeType, args ...string) (string, int, error) {
	defer v.SomeOtherMethod()
	switch len(args) {
	case 0:
		return v.SomeMethod("", ""), 0, nil
	case 1:
		return v.SomeMethod(args[0], ""), 1, nil
	case 2:
		return v.SomeMethod(args[0], args[1]), 2, nil
	default:
		return "", -1, fmt.Errorf("someFunc: unsupported number of arguments: %d", len(args))
	}
}

func main() {
	var t printingT
	someMock := newMockSomeType(t, time.Second)

	type someResult struct {
		result string
		n      int
	}
	// equivalent code without using Go:
	//
	// results := make(chan someResult, 1)
	// errs := make(chan error, 1)
	// go func() {
	//     res, n, err := someFunc(someMock, someArgs...)
	//     errs <- err
	//     results <- someResult{res, n}
	// }()
	results, errs := pers.Go(t, func() (someResult, error) {
		result, n, err := someFunc(someMock, "foo")
		return someResult{result, n}, err
	})

	// At this point, the business logic is running in a separate goroutine.
	// If it calls a method on a mock, it will block waiting for a return.
	// Let's set up an expected method call and provide a return value.

	pers.MethodWasCalled(t, someMock, "SomeMethod",
		pers.Within(time.Second),
		pers.WithArgs("foo", ""),
		pers.Returning("baz"),
	)

	// Within tells the mock to give the business logic time to catch up. Then
	// we check the parameters that SomeMethod was called with before
	// returning "baz".

	// If we get to this point, then we know that the business logic called
	// SomeMethod as we expected it to, and we've returned the value we want to
	// the business logic. Now we can set up another check.

	pers.MethodWasCalled(t, someMock, "SomeOtherMethod",
		pers.Within(time.Second),
		// even if SomeOtherMethod doesn't have return values, the mock will
		// block until we tell it to return.
		pers.Returning(),
	)

	// Eventually, the business logic will return, and we can check those
	// values here.

	err := <-errs
	res := <-results
	if err != nil {
		t.Fatalf("error was non-nil: %v", err)
	}
	if res.result != "baz" {
		t.Fatalf("result was not baz: %v", res)
	}
	t.Logf("success")
}
Output:

success

func MethodWasCalled added in v0.6.3

func MethodWasCalled[T *U, U any](t TestingT, mock T, name string, opts ...MethodAssertOption)

MethodWasCalled is an assertion function for asserting that a method in a hel mock was called. Options may be used as additional assertions about the arguments or to adjust the method's behavior (e.g. cause it to panic or pass along return values).

MethodWasCalled will panic if mock does not have a method matching name.

Certain options may trigger panics in other cases - see the documentation for those options for more detail.

func MethodWasNotCalled added in v0.6.4

func MethodWasNotCalled(t TestingT, mock any, name string, opts ...MethodAssertOption)

MethodWasNotCalled is an invers assertion function of MethodWasCalled.

func Panic added in v0.6.0

func Panic(mock any, withValue any)

Panic enqueues a panic on a mock method call. This panic will be enqueued after any queued returns (using Return).

Panic itself will panic if mock is not a struct with a ret.Panicker field.

func Return

func Return(mock any, args ...any)

Return will enqueue a normal, non-panicking return on a mock method.

Return panics if:

  • the passed in mock value is not a valid mock field (a channel or struct of channels).
  • the passed in args cannot be returned on the mock field.
  • the passed in mock value is already full and sending another return value would block.

Types

type HaveMethodExecutedMatcher

type HaveMethodExecutedMatcher struct {
	MethodName string
	// contains filtered or unexported fields
}

HaveMethodExecutedMatcher is a matcher, intended for use with packages like github.com/poy/onpar/expect, to ensure that a method on a mock was executed.

func HaveMethodExecuted

func HaveMethodExecuted(name string, opts ...MethodAssertOption) *HaveMethodExecutedMatcher

HaveMethodExecuted returns a matcher, intended for use with packages like github.com/poy/onpar/expect, which asserts that the method referenced by name was executed. Options can modify the behavior of the matcher.

HaveMethodExecuted will panic if the mock does not have a method matching name.

The HaveMethodExecutedMatcher will panic if any of the options used don't match the target mock properly. Check the documentation on the options to get more specific information about what would cause the matcher to panic.

func (HaveMethodExecutedMatcher) Match

func (m HaveMethodExecutedMatcher) Match(v any) (any, error)

Match checks the mock value v to see if it has a method matching m.MethodName which has been called.

func (*HaveMethodExecutedMatcher) UseDiffer

func (m *HaveMethodExecutedMatcher) UseDiffer(d matchers.Differ)

UseDiffer sets m to use d when showing a diff between actual and expected values.

type MethodAssertOption added in v0.6.6

type MethodAssertOption func(callPrefs) callPrefs

MethodAssertOption is an option function for the HaveMethodExecutedMatcher.

func Returning

func Returning(vals ...any) MethodAssertOption

Returning returns a HaveMethodExecutedOption which will return the arguments on the mock's return channels after the method in question has been called. With the exception of CallSequence, Return will _not_ immediately return the values - the values will be returned after the method is called.

Returning will panic if:

  • The values provided are not ConvertibleTo the mock's return types.
  • The number of values provided does not match the number of return types in the mock.
  • For variadic methods, the matcher will use vals[len(nonVariadicArgs(mock)):] as the variadic argument.

func StoreArgs

func StoreArgs(targets ...any) MethodAssertOption

StoreArgs returns a HaveMethodExecutedOption which stores the arguments passed to the method in the addresses provided. A nil value tells the matcher to skip the argument at that index. A value of Any is treated the same as a nil value.

To store variadic arguments, you must store the whole variadic slice. StoreArgs does not store individual variadic arguments.

StoreArgs will panic if:

  • The values provided are not pointers, with the exception of Any.
  • The mock's arguments are not ConvertibleTo the targets' types.
  • The number of targets does not match the number of arguments in the method, including the variadic slice (if any).
Example
package main

import (
	"fmt"

	"git.sr.ht/~nelsam/hel/pkg/pers"
)

type fakeMock struct {
	FooCalled chan struct{}
	FooInput  struct {
		Arg0 chan int
		Arg1 chan string
	}
	FooOutput struct {
		Err chan error
	}
	BarCalled chan struct{}
}

func newFakeMock() *fakeMock {
	m := &fakeMock{}
	m.FooCalled = make(chan struct{}, 100)
	m.FooInput.Arg0 = make(chan int, 100)
	m.FooInput.Arg1 = make(chan string, 100)
	m.FooOutput.Err = make(chan error, 100)
	m.BarCalled = make(chan struct{}, 100)
	return m
}

func (m *fakeMock) Foo(arg0 int, arg1 string) error {
	m.FooCalled <- struct{}{}
	m.FooInput.Arg0 <- arg0
	m.FooInput.Arg1 <- arg1
	return <-m.FooOutput.Err
}

func (m *fakeMock) Bar() {
	m.BarCalled <- struct{}{}
}

func main() {
	// Simulate calling a method on a mock
	fm := newFakeMock()
	fm.FooCalled <- struct{}{}
	fm.FooInput.Arg0 <- 42
	fm.FooInput.Arg1 <- "foobar"

	// Provide some addresses to store the arguments
	var (
		arg0 int
		arg1 string
	)
	m := pers.HaveMethodExecuted("Foo", pers.StoreArgs(&arg0, &arg1))
	_, err := m.Match(fm)
	fmt.Println(err)
	fmt.Println(arg0)
	fmt.Println(arg1)
}
Output:

<nil>
42
foobar

func WithArgs

func WithArgs(args ...any) MethodAssertOption

WithArgs returns a MethodAssertOption which tells an assertion to only pass if the method was called with the passed in arguments.

WithArgs supports Any and VariadicAny to ignore a value that you don't want to assert on.

On variadic methods, the expected variadic arguments should be included as values in arguments to WithArgs, not as a slice. So for example, a Printf-style call would be `WithArgs("%d %s", 1, "foo")` instead of `WithArgs("%d %s", []any{1, "foo"})`.

WithArgs will panic if:

  • The argument passed in is not ConvertibleTo the mock's argument type at the matching index.
  • len(args) is less than the number of non-variadic arguments to the method.
  • On non-variadic methods, len(args) is greater than the number of arguments.

func Within

Within returns a MethodAssertOption which tells an assertion to wait the given duration for a method to be called. This is useful when running business logic concurrently with test logic, to allow business logic to catch up.

type OnparMatcher added in v0.6.5

type OnparMatcher interface {
	Match(actual any) (any, error)
}

OnparMatcher is any matcher implementing onpar's matcher type. Some code in this package supports matching against child matchers, for example:

HaveBeenExecuted("Foo", WithArgs(matchers.HaveLen(12)))

type Sequence added in v0.6.5

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

Sequence is a helper for tracking a sequence of calls on various hel mocks. It must be constructed with CallSequence so that assertions will be checked during test cleanup.

Sequence does not check that calls to different mocks happen in exactly the order that the assertions are in. If that level of control is required, we recommend following the concurrent testing pattern, described in the documentation on `Go`.

Sequence will panic if the mock's return channel is already full.

func CallSequence added in v0.6.5

func CallSequence(t TestingT) *Sequence

CallSequence returns a new mock call sequence tracker.

func (*Sequence) Check added in v0.6.6

func (s *Sequence) Check(t TestingT)

Check performs any assertions on s immediately. CallSequence sets this up to run automatically on test cleanup.

Any expectations checked by this will be removed, so it is safe to periodically call Check, then continue adding more expected calls.

type T

type T interface {
	Fatalf(string, ...any)
	Cleanup(func())
}

T represents the methods we need from the testing.T or testing.B types.

type TestingT added in v0.6.3

type TestingT interface {
	Cleanup(func())
	Fatalf(format string, args ...any)
}

Jump to

Keyboard shortcuts

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