matchers

package
v3.0.1 Latest Latest
Warning

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

Go to latest
Published: Aug 29, 2022 License: Apache-2.0 Imports: 7 Imported by: 0

Documentation

Overview

Package matchers provides a flexible test assertion API similar to Java's Hamcrest. Matchers are constructed separately from the values being tested, and can then be applied to any value, or negated, or combined in various ways.

This implementation is for Go 1.17 so it does not yet have generics. Instead, all matchers take values of type interface{} and must explicitly cast the type if needed. The simplest way to provide type safety is to use Matcher.EnsureType().

Examples of syntax:

import m "github.com/launchdarkly/go-test-helpers/matchers"

func TestSomething(t *T) {
    eventData := []string{
        `{"kind": "feature", "value": true}`,
        `{"key": "x", "kind": "custom"}`,
    }
    m.For(t, "event data").Assert(eventData, m.ItemsInAnyOrder(
        m.JSONStrEqual(`{"kind": "custom", "key": "x"}`),
        m.JSONStrEqual(`{"kind": "feature", "value": true}`),
    ))
    m.For(t, "first event").Assert(eventData[0],
        m.JSONProperty("kind").Should(m.Not(m.Equal("summary"))))
}

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DescribeValue

func DescribeValue(value interface{}) string

DescribeValue tries to create attractive string representations of values for test failure messages. The logic is as follows (whichever comes first):

If the value is nil, it returns "nil".

If the type is a struct that has "json" field tags, it is converted to JSON.

If the type implements fmt.Stringer, its String method is called.

If the type is string, it is quoted, unless it already has bracket or brace delimiters.

If the type is []byte, it is converted to a string unchanged, unless it is valid JSON in which case it is passed to jsonhelpers.CanonicalizeJSON.

If the type is json.RawMessage, it is passed to jsonhelpers.CanonicalizeJSON.

If the type is a slice or array, it is formatted as [value1, value2, value3] (unlike Go's default formatting which has no commas) and each value is recursively formatted with DescribeValue.

At last resort, it is formatted with fmt.Sprintf("%+v").

Types

type AssertionScope

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

AssertionScope is a context for executing assertions.

func For

func For(t TestingT, name string) AssertionScope

For is the same as In, but adds a descriptive name in front of whatever assertions are done. In this example, a failure would be logged as "score: does not equal 2" rather than only "does not equal 2".

func TestSomething(t *testing.T) {
    matchers.For(t, "score").Assert(x, matchers.Equal(2))
}

func In

In is for use with any test framework that has a test scope type with the same basic methods as Go's testing.T (as defined by the TestingT interface). Any calls to Assert or Require on the returned AssertionScope will update the state of t.

func TestSomething(t *testing.T) {
    matchers.In(t).Assert(x, matchers.Equal(2))
}

See also For.

func (AssertionScope) Assert

func (a AssertionScope) Assert(value interface{}, matcher Matcher) bool

Assert is for use with any test framework that has a test scope type with the same Errorf method as Go's testing.T. It tests a value against a matcher and, on failure, calls the test scope's Errorf method. This logs a failure but does not stop the test.

func (AssertionScope) For

func (a AssertionScope) For(name string) AssertionScope

For returns a new AssertionScope that has an additional name prefix. In this example, a failure would be logged as "final: score: does not equal 2" rather than only "does not equal 2".

func TestSomething(t *testing.T) {
    matchers.In(t).For("final").For("score").Assert(x, matchers.Equal(2))
}

func (AssertionScope) Require

func (a AssertionScope) Require(value interface{}, matcher Matcher) bool

Require is for use with any test framework that has a test scope type with the same Errorf and FailNow methods as Go's testing.T. It tests a value against a matcher and, on failure, calls the test scope's Errorf method and then FailNow. This logs a failure and immediately terminates the test.

type DescribeFailureFunc

type DescribeFailureFunc func(value interface{}) string

DescribeFailureFunc is a function used in defining a new Matcher. Given the value that was tested, and assuming that the test failed, it returns a descriptive string.

For simple conditions, this function can be omitted or can return an empty string, in which case the failure description will be produced from only the DescribeTestFunc and a description of the test value

The second parameter is the function to use for making a string description of a value of the expected type.

type DescribeTestFunc

type DescribeTestFunc func() string

DescribeTestFunc is a function used in defining a new Matcher. It returns a description of the test expectation.

type KeyValueMatcher

type KeyValueMatcher struct {
	Key   interface{}
	Value Matcher
}

KeyValueMatcher is used with MapOf or MapIncluding to describe a matcher for a key-value pair in a map.

func KV

func KV(key interface{}, valueMatcher Matcher) KeyValueMatcher

KV is a shortcut for constructing a KeyValueMatcher for use with MapOf or MapIncluding.

type Matcher

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

Matcher is a general mechanism for declaring expectations about a value. Expectations can be combined, and they self-describe on failure.

func AllOf

func AllOf(matchers ...Matcher) Matcher

AllOf requires that the input value passes all of the specified Matchers. If it fails, the failure message describes all of the Matchers that failed.

func AnyOf

func AnyOf(matchers ...Matcher) Matcher

AnyOf requires that the input value passes at least one of the specified Matchers. It will not execute any further matches after the first pass. If it fails all of them, the failure message describes all of the failure conditions.

func BeNil

func BeNil() Matcher

BeNil is a matcher that passes if the value is a nil interface value, a nil pointer, a nil slice, or a nil map.

func Equal

func Equal(expectedValue interface{}) Matcher

Equal is a matcher that tests whether the input value matches the expected value according to reflect.DeepEqual, except in the case of numbers where an exact type match is not needed.

func Items

func Items(matchers ...Matcher) Matcher

Items is a matcher for a slice or array value. It tests that the number of elements is equal to the number of matchers, and that each element matches the corresponding matcher in order.

s := []int{6,2}
matchers.Items(matchers.Equal(6), matchers.Equal(2)).Test(s) // pass
matchers.Items(matchers.Equal(2), matchers.Equal(6)).Test(s) // fail

func ItemsInAnyOrder

func ItemsInAnyOrder(matchers ...Matcher) Matcher

ItemsInAnyOrder is a matcher for a slice or array value. It tests that the number of elements is equal to the number of matchers, and that each matcher matches an element.

s := []int{6,2}
matchers.ItemsInAnyOrder(matchers.Equal(2), matchers.Equal(6)).Test(s) // pass

func JSONEqual

func JSONEqual(expectedValue interface{}) Matcher

JSONEqual is similar to Equal but with richer behavior for JSON values.

Both the expected value and the actual value can be of any type. If the type is either []byte or json.RawMessage, it will be interpreted as JSON which will be parsed; for all other types, it will be first serialized to JSON with json.Marshal and then parsed. Then the parsed values or data structures are tested for deep equality. For instance, this test passes:

matchers.In(t).Assert([]byte(`{"a": true, "b": false`),
    matchers.JSONEqual(map[string]bool{b: false, a: true}))

The shortcut JSONEqualStr can be used to avoid writing []byte() if the expected value is already a serialized JSON string.

func JSONStrEqual

func JSONStrEqual(expectedValue string) Matcher

JSONStrEqual is equivalent to JSONEqual except that it converts expectedValue from string to []byte first, and if the input value is a string it does the same. This is convenient if you are matching against already-serialized JSON, because otherwise passing a string value to JSONEqual would cause that value to be serialized in the way JSON represents strings, that is, with quoting and escaping.

matchers.In(t).Assert(`{"a": true, "b": false`,
    matchers.JSONStrEqual(`{"b": false, "a": true}`)

func MapIncluding

func MapIncluding(keyValueMatchers ...KeyValueMatcher) Matcher

MapIncluding is a matcher for a map value. It tests that the map contains all of the keys in the specified list, and that the matcher for each key is satisfied by the corresponding value. The map may also contain additional keys.

m := map[string]int{"a": 6, "b": 2}
matchers.MapOf(
    matchers.KV("a", matchers.Equal(2)),
    matchers.KV("b", matchers.Equal(6)),
}).Test(s) // pass

func MapOf

func MapOf(keyValueMatchers ...KeyValueMatcher) Matcher

MapOf is a matcher for a map value. It tests that the map has exactly the same keys as the specified list, and that the matcher for each key is satisfied by the corresponding value.

m := map[string]int{"a": 6, "b": 2}
matchers.MapOf(
    matchers.KV("a", matchers.Equal(2)),
    matchers.KV("b", matchers.Equal(6)),
}).Test(s) // pass

func New

func New(
	test TestFunc,
	describeTest DescribeTestFunc,
	describeFailure DescribeFailureFunc,
) Matcher

New creates a Matcher.

func Not

func Not(matcher Matcher) Matcher

Not negates the result of another Matcher.

matchers.Not(Equal(3)).Assert(t, 4)
// failure message will describe expectation as "not (equal to 3)"

func StringContains

func StringContains(substring string) Matcher

StringContains is a matcher for string values that tests for the presence of a substring, case-sensitively.

func StringHasPrefix

func StringHasPrefix(prefix string) Matcher

StringHasPrefix is a matcher for string values that calls strings.HasPrefix.

func StringHasSuffix

func StringHasSuffix(suffix string) Matcher

StringHasSuffix is a matcher for string values that calls strings.HasSuffix.

func (Matcher) EnsureType

func (m Matcher) EnsureType(valueOfType interface{}) Matcher

EnsureType adds type safety to a matcher. The valueOfType parameter should be any value of the expected type. The returned Matcher will guarantee that the value is of that type before calling the original test function, so it is safe for the test function to cast the value.

func (Matcher) Test

func (m Matcher) Test(value interface{}) (pass bool, failDescription string)

Test executes the expectation for a specific value. It returns true if the value passes the test or false for failure, plus a string describing the expectation that failed.

type MatcherTransform

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

MatcherTransform is a combinator that allows an input value to be transformed to some other value (possibly of a different type) before being tested by other Matchers.

For instance, this could be used to access a field inside a struct or some other nested data structure. Assuming there is a struct type S with a field F, you could do this:

SF := matchers.Transform("F",
    func(value interface{}) interface{} { return value.(S).F })
SF.Should(Equal(3)).Assert(t, someInstanceOfS)

The advantages of doing this, instead of simply getting the F field directly and testing it, are 1. you can use combinators such as AllOf to test multiple properties in a single assertion, and 2. failure messages will automatically include both a full description of someInstanceOfS and an explanation of what was wrong with it. For instance, in the example above, if someInstanceOfS.F was really 4, the failure message would show:

expected: F equal to 3
actual value was: {F: 4}

You can use MatcherTransform's other methods to add type safety.

func JSONArray

func JSONArray() MatcherTransform

JSONArray is a MatcherTransform that takes any value serializable as a JSON array, and converts it to []interface{} slice; then you can apply a matcher to that slice. It fails if the value is not serializable as a JSON array.

myArray := []byte(`["a", "b", "c"]`)
matchers.In(t).Assert(myArray,
    matchers.JSONArray().Should(matchers.Length().Should(matchers.Equal(3))))

func JSONMap

func JSONMap() MatcherTransform

JSONMap is a MatcherTransform that takes any value serializable as a JSON object, and converts it to a map[interface{}]interface{}; then you can apply a matcher to that map. It fails if the value is not serializable as a JSON object.

myArray := []byte(`{"a": 1, "b": "xyz"}`)
matchers.In(t).Assert(myJSON,
    matchers.JSONMap().Should(
        matchers.MapOf(
            matchers.KV("a", matchers.Equal(1)),
            matchers.KV("b", matchers.StringHasPrefix("x")),
        )))

func JSONOptProperty

func JSONOptProperty(name string) MatcherTransform

JSONOptProperty is the same as JSONProperty, but if the property does not exist, it treats it as a nil value rather than error.

func JSONProperty

func JSONProperty(name string) MatcherTransform

JSONProperty is a MatcherTransform that takes any value serializable as a JSON object and gets a named property from it; then you can apply a matcher to the value of that property. It fails if no such property exists (see OptJSONProperty).

myObject := []byte(`{"a": {"b": 2}}`)
matchers.In(t).Assert(myObject,
    matchers.JSONProperty("a").Should(
        matchers.JSONProperty("b").Should(Equal(2))))

An alternative is to use JSONMap combined with MapOf or MapIncluding.

func Length

func Length() MatcherTransform

Length is a MatcherTransform that takes any value len() can operate on, gets its length, and applies some matcher to the result.

func OptValueForKey

func OptValueForKey(key interface{}) MatcherTransform

OptValueForKey is a MatcherTransform that takes a map, looks up a value in it by key, and applies a matcher to that value. If no such key exists, it returns the zero value for the type. If the map was nil, it returns nil.

myMap := map[string]map[string]int{"a": map[string]int{"b": 2}}
matchers.In(t).Assert(myMap,
    matchers.OptValueForKey("a").Should(
        matchers.OptValueForKey("c").Should(Equal(0))))

func Transform

func Transform(
	name string,
	getValue func(interface{}) (interface{}, error),
) MatcherTransform

Transform creates a MatcherTransform. The name parameter is a brief description of what the output value is in relation to the input value (for instance, if you are getting a field called F from a struct, it could simply be "F"); it will be prefixed to the description of any Matcher that you use with Should(). The getValue parameter is a function that transforms the original value into the value you will be testing.

func ValueForKey

func ValueForKey(key interface{}) MatcherTransform

ValueForKey is a MatcherTransform that takes a map, looks up a value in it by key, and applies a matcher to that value. It fails if no such key exists (see OptValueForKey).

myMap := map[string]map[string]int{"a": map[string]int{"b": 2}}
matchers.In(t).Assert(myObject,
    matchers.ValueForKey("a").Should(
        matchers.ValueForKey("b").Should(Equal(2))))

func (MatcherTransform) EnsureInputValueType

func (mt MatcherTransform) EnsureInputValueType(valueOfType interface{}) MatcherTransform

EnsureInputValueType is the equivalent of Matcher.EnsureValueType. Given any value of the desired type, it returns a modified MatcherTransform that will safely fail if the wrong type is passed in.

stringLength := matchers.Transform("string length",
    func(value interface{}) interface{} { return len(value.(string)) }).
    EnsureInputValueType("")

func (MatcherTransform) Should

func (mt MatcherTransform) Should(matcher Matcher) Matcher

Should applies a Matcher to the transformed value. That is, assuming that this MatcherTransform converts an A value into a B value, mt.Should(Equal(3)) returns a Matcher that takes A, converts it to B, and applies Equal(3) to B.

type TestFunc

type TestFunc func(value interface{}) bool

TestFunc is a function used in defining a new Matcher. It returns true if the value passes the test or false for failure.

type TestingT

type TestingT interface {
	Errorf(format string, args ...interface{})
	FailNow()
}

TestingT is an interface for any test scope type that has an Errorf method for reporting failures, and a FailNow method for stopping the test immediately. This is compatible with Go's testing.T, and with assert.TestingT and require.TestingT. See Test and For.

Jump to

Keyboard shortcuts

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