Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

assert: Add a self-describing Matcher #1507

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions assert/assertion_format.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions assert/assertion_forward.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions assert/assertions.go
Expand Up @@ -2066,3 +2066,28 @@ func buildErrorChainString(err error) string {
}
return chain
}

// A Matcher is an object that can assert and describe an arbitrary complex condition.
// This allows the assert framework to be extended in specialised ways while maintaining
// clear error reporting.
type Matcher interface {
// Match returns true if the given actual value meets the condition embodied
// in this Matcher.
Match(actual interface{}) bool
// Describe is called when the test fails to get a descriptive error message.
// It should succinctly describe the condition that is being expected.
Describe() string
}

// Matches asserts that the actual value meets the condition specified by matcher.
func Matches(t TestingT, matcher Matcher, actual interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}

if matcher.Match(actual) {
return true
}
_, act := formatUnequalValues(nil, actual)
return Fail(t, fmt.Sprintf("Not matching:\nexpected: %s\nactual : %s", matcher.Describe(), act), msgAndArgs...)
}
17 changes: 17 additions & 0 deletions assert/assertions_test.go
Expand Up @@ -3145,3 +3145,20 @@ func TestIsNil(t *testing.T) {
t.Fatal("fail")
}
}

func TestMatcher(t *testing.T) {
for _, tt := range []struct {
str string
expected bool
msg string
}{
{"foobar", true, ""},
{"wibble", false, "Not matching:\nexpected: a string starting with \"foo\"\nactual : string(\"wibble\")\n"},
} {
t.Run(tt.str, func(t *testing.T) {
mockT := new(captureTestingT)
res := Matches(mockT, StringStarting("foo"), tt.str)
mockT.checkResultAndErrMsg(t, res, tt.expected, tt.msg)
})
}
}
48 changes: 48 additions & 0 deletions assert/example_matcher_test.go
@@ -0,0 +1,48 @@
package assert

import (
"fmt"
"regexp"
"strings"
)

// stringStartsWith is a type that implements Matcher to test the actual
// string has the expected prefix.
type stringStartsWith struct {
expected string
}

func (s *stringStartsWith) Match(actual interface{}) bool {
str, isStr := actual.(string)
return isStr && strings.HasPrefix(str, s.expected)
}

func (s *stringStartsWith) Describe() string {
return fmt.Sprintf("a string starting with %q", s.expected)
}

// StringStarting returns a Matcher that asserts that the actual value
// is a string that has the expected prefix.
//
// Wrapping the matcher type in a factory function is just a little syntactic sugar for
// its use in assert.Matches
func StringStarting(e string) Matcher {
return &stringStartsWith{expected: e}
}

// Remove trailing whitespace from the message string, because govet
// removes it from the example comment
var stripTrailingSpace = regexp.MustCompile("(\\s+)\n")

func ExampleMatcher() {
t := &captureTestingT{} // Usually this would be the *testing.T provided by the test

Matches(t, StringStarting("goodbye"), "hello world")

fmt.Println(stripTrailingSpace.ReplaceAllString(t.msg, "\n"))
// Output:
// Error Trace:
// Error: Not matching:
// expected: a string starting with "goodbye"
// actual : string("hello world")
}
22 changes: 22 additions & 0 deletions require/require.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions require/require_forward.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.