Merge branch 'master' into mockery_docs

pull/1346/head
Olivier Mengué 2023-07-05 12:49:36 +02:00 committed by GitHub
commit 4f6e609334
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1955 additions and 900 deletions

View File

@ -6,7 +6,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
go_version: ["1.18.1", "1.17.6", "1.16.5"]
go_version: ["1.20", "1.19"]
steps:
- uses: actions/checkout@v3
- name: Setup Go

19
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,19 @@
name: Create release from new tag
# this flow will be run only when new tags are pushed that match our pattern
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Create GitHub release from tag
uses: softprops/action-gh-release@v1

View File

@ -5,5 +5,4 @@ pull requests.
* @glesica
* @boyan-soubachov
* @mvdkleijn

View File

@ -16,14 +16,14 @@ Features include:
Get started:
* Install testify with [one line of code](#installation), or [update it with another](#staying-up-to-date)
* For an introduction to writing test code in Go, see http://golang.org/doc/code.html#Testing
* Check out the API Documentation http://godoc.org/github.com/stretchr/testify
* To make your testing life easier, check out our other project, [gorc](http://github.com/stretchr/gorc)
* A little about [Test-Driven Development (TDD)](http://en.wikipedia.org/wiki/Test-driven_development)
* For an introduction to writing test code in Go, see https://go.dev/doc/code#Testing
* Check out the API Documentation https://pkg.go.dev/github.com/stretchr/testify
* To make your testing life easier, check out our other project, [gorc](https://github.com/stretchr/gorc)
* A little about [Test-Driven Development (TDD)](https://en.wikipedia.org/wiki/Test-driven_development)
[`assert`](http://godoc.org/github.com/stretchr/testify/assert "API documentation") package
[`assert`](https://pkg.go.dev/github.com/stretchr/testify/assert "API documentation") package
-------------------------------------------------------------------------------------------
The `assert` package provides some helpful methods that allow you to write better test code in Go.
@ -100,19 +100,21 @@ func TestSomething(t *testing.T) {
}
```
[`require`](http://godoc.org/github.com/stretchr/testify/require "API documentation") package
[`require`](https://pkg.go.dev/github.com/stretchr/testify/require "API documentation") package
---------------------------------------------------------------------------------------------
The `require` package provides same global functions as the `assert` package, but instead of returning a boolean result they terminate current test.
These functions must be called from the goroutine running the test or benchmark function, not from other goroutines created during the test.
Otherwise race conditions may occur.
See [t.FailNow](http://golang.org/pkg/testing/#T.FailNow) for details.
See [t.FailNow](https://pkg.go.dev/testing#T.FailNow) for details.
[`mock`](http://godoc.org/github.com/stretchr/testify/mock "API documentation") package
[`mock`](https://pkg.go.dev/github.com/stretchr/testify/mock "API documentation") package
----------------------------------------------------------------------------------------
The `mock` package provides a mechanism for easily writing mock objects that can be used in place of real objects when writing test code.
An example test function that tests a piece of code that relies on an external object `testObj`, can setup expectations (testify) and assert that they indeed happened:
An example test function that tests a piece of code that relies on an external object `testObj`, can set up expectations (testify) and assert that they indeed happened:
```go
package yours
@ -217,14 +219,14 @@ func TestSomethingElse2(t *testing.T) {
}
```
For more information on how to write mock code, check out the [API documentation for the `mock` package](http://godoc.org/github.com/stretchr/testify/mock).
For more information on how to write mock code, check out the [API documentation for the `mock` package](https://pkg.go.dev/github.com/stretchr/testify/mock).
You can use the [mockery tool](https://vektra.github.io/mockery/latest/) to autogenerate the mock code against an interface as well, making using mocks much quicker.
[`suite`](http://godoc.org/github.com/stretchr/testify/suite "API documentation") package
[`suite`](https://pkg.go.dev/github.com/stretchr/testify/suite "API documentation") package
-----------------------------------------------------------------------------------------
The `suite` package provides functionality that you might be used to from more common object oriented languages. With it, you can build a testing suite as a struct, build setup/teardown methods and testing methods on your struct, and run them with 'go test' as per normal.
The `suite` package provides functionality that you might be used to from more common object-oriented languages. With it, you can build a testing suite as a struct, build setup/teardown methods and testing methods on your struct, and run them with 'go test' as per normal.
An example suite is shown below:
@ -265,7 +267,7 @@ func TestExampleTestSuite(t *testing.T) {
For a more complete example, using all of the functionality provided by the suite package, look at our [example testing suite](https://github.com/stretchr/testify/blob/master/suite/suite_test.go)
For more information on writing suites, check out the [API documentation for the `suite` package](http://godoc.org/github.com/stretchr/testify/suite).
For more information on writing suites, check out the [API documentation for the `suite` package](https://pkg.go.dev/github.com/stretchr/testify/suite).
`Suite` object has assertion methods:
@ -348,7 +350,7 @@ To update Testify to the latest version, use `go get -u github.com/stretchr/test
Supported go versions
==================
We currently support the most recent major Go versions from 1.13 onward.
We currently support the most recent major Go versions from 1.19 onward.
------

View File

@ -312,7 +312,7 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
break
}
// time.Time can compared!
// time.Time can be compared!
timeObj1, ok := obj1.(time.Time)
if !ok {
timeObj1 = obj1Value.Convert(timeType).Interface().(time.Time)

View File

@ -90,6 +90,23 @@ func EqualErrorf(t TestingT, theError error, errString string, msg string, args
return EqualError(t, theError, errString, append([]interface{}{msg}, args...)...)
}
// EqualExportedValuesf asserts that the types of two objects are equal and their public
// fields are also equal. This is useful for comparing structs that have private fields
// that could potentially differ.
//
// type S struct {
// Exported int
// notExported int
// }
// assert.EqualExportedValuesf(t, S{1, 2}, S{1, 3}, "error message %s", "formatted") => true
// assert.EqualExportedValuesf(t, S{1, 2}, S{2, 3}, "error message %s", "formatted") => false
func EqualExportedValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return EqualExportedValues(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// EqualValuesf asserts that two objects are equal or convertable to the same types
// and equal.
//
@ -155,6 +172,31 @@ func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick
return Eventually(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...)
}
// EventuallyWithTf asserts that given condition will be met in waitFor time,
// periodically checking target function each tick. In contrast to Eventually,
// it supplies a CollectT to the condition function, so that the condition
// function can use the CollectT to call other assertions.
// The condition is considered "met" if no errors are raised in a tick.
// The supplied CollectT collects all errors from one tick (if there are any).
// If the condition is not met before waitFor, the collected errors of
// the last tick are copied to t.
//
// externalValue := false
// go func() {
// time.Sleep(8*time.Second)
// externalValue = true
// }()
// assert.EventuallyWithTf(t, func(c *assert.CollectT, "error message %s", "formatted") {
// // add assertions as needed; any assertion failure will fail the current tick
// assert.True(c, externalValue, "expected 'externalValue' to be true")
// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false")
func EventuallyWithTf(t TestingT, condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return EventuallyWithT(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...)
}
// Exactlyf asserts that two objects are equal in value and type.
//
// assert.Exactlyf(t, int32(123), int64(123), "error message %s", "formatted")

View File

@ -155,6 +155,40 @@ func (a *Assertions) EqualErrorf(theError error, errString string, msg string, a
return EqualErrorf(a.t, theError, errString, msg, args...)
}
// EqualExportedValues asserts that the types of two objects are equal and their public
// fields are also equal. This is useful for comparing structs that have private fields
// that could potentially differ.
//
// type S struct {
// Exported int
// notExported int
// }
// a.EqualExportedValues(S{1, 2}, S{1, 3}) => true
// a.EqualExportedValues(S{1, 2}, S{2, 3}) => false
func (a *Assertions) EqualExportedValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return EqualExportedValues(a.t, expected, actual, msgAndArgs...)
}
// EqualExportedValuesf asserts that the types of two objects are equal and their public
// fields are also equal. This is useful for comparing structs that have private fields
// that could potentially differ.
//
// type S struct {
// Exported int
// notExported int
// }
// a.EqualExportedValuesf(S{1, 2}, S{1, 3}, "error message %s", "formatted") => true
// a.EqualExportedValuesf(S{1, 2}, S{2, 3}, "error message %s", "formatted") => false
func (a *Assertions) EqualExportedValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return EqualExportedValuesf(a.t, expected, actual, msg, args...)
}
// EqualValues asserts that two objects are equal or convertable to the same types
// and equal.
//
@ -288,6 +322,56 @@ func (a *Assertions) Eventually(condition func() bool, waitFor time.Duration, ti
return Eventually(a.t, condition, waitFor, tick, msgAndArgs...)
}
// EventuallyWithT asserts that given condition will be met in waitFor time,
// periodically checking target function each tick. In contrast to Eventually,
// it supplies a CollectT to the condition function, so that the condition
// function can use the CollectT to call other assertions.
// The condition is considered "met" if no errors are raised in a tick.
// The supplied CollectT collects all errors from one tick (if there are any).
// If the condition is not met before waitFor, the collected errors of
// the last tick are copied to t.
//
// externalValue := false
// go func() {
// time.Sleep(8*time.Second)
// externalValue = true
// }()
// a.EventuallyWithT(func(c *assert.CollectT) {
// // add assertions as needed; any assertion failure will fail the current tick
// assert.True(c, externalValue, "expected 'externalValue' to be true")
// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false")
func (a *Assertions) EventuallyWithT(condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return EventuallyWithT(a.t, condition, waitFor, tick, msgAndArgs...)
}
// EventuallyWithTf asserts that given condition will be met in waitFor time,
// periodically checking target function each tick. In contrast to Eventually,
// it supplies a CollectT to the condition function, so that the condition
// function can use the CollectT to call other assertions.
// The condition is considered "met" if no errors are raised in a tick.
// The supplied CollectT collects all errors from one tick (if there are any).
// If the condition is not met before waitFor, the collected errors of
// the last tick are copied to t.
//
// externalValue := false
// go func() {
// time.Sleep(8*time.Second)
// externalValue = true
// }()
// a.EventuallyWithTf(func(c *assert.CollectT, "error message %s", "formatted") {
// // add assertions as needed; any assertion failure will fail the current tick
// assert.True(c, externalValue, "expected 'externalValue' to be true")
// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false")
func (a *Assertions) EventuallyWithTf(condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return EventuallyWithTf(a.t, condition, waitFor, tick, msg, args...)
}
// Eventuallyf asserts that given condition will be met in waitFor time,
// periodically checking target function each tick.
//

View File

@ -19,7 +19,7 @@ import (
"github.com/davecgh/go-spew/spew"
"github.com/pmezard/go-difflib/difflib"
yaml "gopkg.in/yaml.v3"
"gopkg.in/yaml.v3"
)
//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=assert -template=assertion_format.go.tmpl"
@ -75,6 +75,77 @@ func ObjectsAreEqual(expected, actual interface{}) bool {
return bytes.Equal(exp, act)
}
// copyExportedFields iterates downward through nested data structures and creates a copy
// that only contains the exported struct fields.
func copyExportedFields(expected interface{}) interface{} {
if isNil(expected) {
return expected
}
expectedType := reflect.TypeOf(expected)
expectedKind := expectedType.Kind()
expectedValue := reflect.ValueOf(expected)
switch expectedKind {
case reflect.Struct:
result := reflect.New(expectedType).Elem()
for i := 0; i < expectedType.NumField(); i++ {
field := expectedType.Field(i)
isExported := field.IsExported()
if isExported {
fieldValue := expectedValue.Field(i)
if isNil(fieldValue) || isNil(fieldValue.Interface()) {
continue
}
newValue := copyExportedFields(fieldValue.Interface())
result.Field(i).Set(reflect.ValueOf(newValue))
}
}
return result.Interface()
case reflect.Ptr:
result := reflect.New(expectedType.Elem())
unexportedRemoved := copyExportedFields(expectedValue.Elem().Interface())
result.Elem().Set(reflect.ValueOf(unexportedRemoved))
return result.Interface()
case reflect.Array, reflect.Slice:
result := reflect.MakeSlice(expectedType, expectedValue.Len(), expectedValue.Len())
for i := 0; i < expectedValue.Len(); i++ {
index := expectedValue.Index(i)
if isNil(index) {
continue
}
unexportedRemoved := copyExportedFields(index.Interface())
result.Index(i).Set(reflect.ValueOf(unexportedRemoved))
}
return result.Interface()
case reflect.Map:
result := reflect.MakeMap(expectedType)
for _, k := range expectedValue.MapKeys() {
index := expectedValue.MapIndex(k)
unexportedRemoved := copyExportedFields(index.Interface())
result.SetMapIndex(k, reflect.ValueOf(unexportedRemoved))
}
return result.Interface()
default:
return expected
}
}
// ObjectsExportedFieldsAreEqual determines if the exported (public) fields of two objects are
// considered equal. This comparison of only exported fields is applied recursively to nested data
// structures.
//
// This function does no assertion of any kind.
func ObjectsExportedFieldsAreEqual(expected, actual interface{}) bool {
expectedCleaned := copyExportedFields(expected)
actualCleaned := copyExportedFields(actual)
return ObjectsAreEqualValues(expectedCleaned, actualCleaned)
}
// ObjectsAreEqualValues gets whether two objects are equal, or if their
// values are equal.
func ObjectsAreEqualValues(expected, actual interface{}) bool {
@ -195,7 +266,7 @@ func messageFromMsgAndArgs(msgAndArgs ...interface{}) string {
// Aligns the provided message so that all lines after the first line start at the same location as the first line.
// Assumes that the first line starts at the correct location (after carriage return, tab, label, spacer and tab).
// The longestLabelLen parameter specifies the length of the longest label in the output (required becaues this is the
// The longestLabelLen parameter specifies the length of the longest label in the output (required because this is the
// basis on which the alignment occurs).
func indentMessageLines(message string, longestLabelLen int) string {
outBuf := new(bytes.Buffer)
@ -425,7 +496,7 @@ func samePointers(first, second interface{}) bool {
// representations appropriate to be presented to the user.
//
// If the values are not of like type, the returned strings will be prefixed
// with the type name, and the value will be enclosed in parenthesis similar
// with the type name, and the value will be enclosed in parentheses similar
// to a type conversion in the Go grammar.
func formatUnequalValues(expected, actual interface{}) (e string, a string) {
if reflect.TypeOf(expected) != reflect.TypeOf(actual) {
@ -473,6 +544,50 @@ func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interfa
}
// EqualExportedValues asserts that the types of two objects are equal and their public
// fields are also equal. This is useful for comparing structs that have private fields
// that could potentially differ.
//
// type S struct {
// Exported int
// notExported int
// }
// assert.EqualExportedValues(t, S{1, 2}, S{1, 3}) => true
// assert.EqualExportedValues(t, S{1, 2}, S{2, 3}) => false
func EqualExportedValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
aType := reflect.TypeOf(expected)
bType := reflect.TypeOf(actual)
if aType != bType {
return Fail(t, fmt.Sprintf("Types expected to match exactly\n\t%v != %v", aType, bType), msgAndArgs...)
}
if aType.Kind() != reflect.Struct {
return Fail(t, fmt.Sprintf("Types expected to both be struct \n\t%v != %v", aType.Kind(), reflect.Struct), msgAndArgs...)
}
if bType.Kind() != reflect.Struct {
return Fail(t, fmt.Sprintf("Types expected to both be struct \n\t%v != %v", bType.Kind(), reflect.Struct), msgAndArgs...)
}
expected = copyExportedFields(expected)
actual = copyExportedFields(actual)
if !ObjectsAreEqualValues(expected, actual) {
diff := diff(expected, actual)
expected, actual = formatUnequalValues(expected, actual)
return Fail(t, fmt.Sprintf("Not equal (comparing only exported fields): \n"+
"expected: %s\n"+
"actual : %s%s", expected, actual, diff), msgAndArgs...)
}
return true
}
// Exactly asserts that two objects are equal in value and type.
//
// assert.Exactly(t, int32(123), int64(123))
@ -794,10 +909,10 @@ func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{})
ok, found := containsElement(s, contains)
if !ok {
return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", s), msgAndArgs...)
return Fail(t, fmt.Sprintf("%#v could not be applied builtin len()", s), msgAndArgs...)
}
if found {
return Fail(t, fmt.Sprintf("\"%s\" should not contain \"%s\"", s, contains), msgAndArgs...)
return Fail(t, fmt.Sprintf("%#v should not contain %#v", s, contains), msgAndArgs...)
}
return true
@ -816,49 +931,44 @@ func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok
return true // we consider nil to be equal to the nil set
}
defer func() {
if e := recover(); e != nil {
ok = false
}
}()
listKind := reflect.TypeOf(list).Kind()
subsetKind := reflect.TypeOf(subset).Kind()
if listKind != reflect.Array && listKind != reflect.Slice && listKind != reflect.Map {
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", list, listKind), msgAndArgs...)
}
subsetKind := reflect.TypeOf(subset).Kind()
if subsetKind != reflect.Array && subsetKind != reflect.Slice && listKind != reflect.Map {
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...)
}
subsetValue := reflect.ValueOf(subset)
if subsetKind == reflect.Map && listKind == reflect.Map {
listValue := reflect.ValueOf(list)
subsetKeys := subsetValue.MapKeys()
subsetMap := reflect.ValueOf(subset)
actualMap := reflect.ValueOf(list)
for i := 0; i < len(subsetKeys); i++ {
subsetKey := subsetKeys[i]
subsetElement := subsetValue.MapIndex(subsetKey).Interface()
listElement := listValue.MapIndex(subsetKey).Interface()
for _, k := range subsetMap.MapKeys() {
ev := subsetMap.MapIndex(k)
av := actualMap.MapIndex(k)
if !ObjectsAreEqual(subsetElement, listElement) {
return Fail(t, fmt.Sprintf("\"%s\" does not contain \"%s\"", list, subsetElement), msgAndArgs...)
if !av.IsValid() {
return Fail(t, fmt.Sprintf("%#v does not contain %#v", list, subset), msgAndArgs...)
}
if !ObjectsAreEqual(ev.Interface(), av.Interface()) {
return Fail(t, fmt.Sprintf("%#v does not contain %#v", list, subset), msgAndArgs...)
}
}
return true
}
for i := 0; i < subsetValue.Len(); i++ {
element := subsetValue.Index(i).Interface()
subsetList := reflect.ValueOf(subset)
for i := 0; i < subsetList.Len(); i++ {
element := subsetList.Index(i).Interface()
ok, found := containsElement(list, element)
if !ok {
return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", list), msgAndArgs...)
return Fail(t, fmt.Sprintf("%#v could not be applied builtin len()", list), msgAndArgs...)
}
if !found {
return Fail(t, fmt.Sprintf("\"%s\" does not contain \"%s\"", list, element), msgAndArgs...)
return Fail(t, fmt.Sprintf("%#v does not contain %#v", list, element), msgAndArgs...)
}
}
@ -877,34 +987,28 @@ func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{})
return Fail(t, "nil is the empty set which is a subset of every set", msgAndArgs...)
}
defer func() {
if e := recover(); e != nil {
ok = false
}
}()
listKind := reflect.TypeOf(list).Kind()
subsetKind := reflect.TypeOf(subset).Kind()
if listKind != reflect.Array && listKind != reflect.Slice && listKind != reflect.Map {
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", list, listKind), msgAndArgs...)
}
subsetKind := reflect.TypeOf(subset).Kind()
if subsetKind != reflect.Array && subsetKind != reflect.Slice && listKind != reflect.Map {
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...)
}
subsetValue := reflect.ValueOf(subset)
if subsetKind == reflect.Map && listKind == reflect.Map {
listValue := reflect.ValueOf(list)
subsetKeys := subsetValue.MapKeys()
subsetMap := reflect.ValueOf(subset)
actualMap := reflect.ValueOf(list)
for i := 0; i < len(subsetKeys); i++ {
subsetKey := subsetKeys[i]
subsetElement := subsetValue.MapIndex(subsetKey).Interface()
listElement := listValue.MapIndex(subsetKey).Interface()
for _, k := range subsetMap.MapKeys() {
ev := subsetMap.MapIndex(k)
av := actualMap.MapIndex(k)
if !ObjectsAreEqual(subsetElement, listElement) {
if !av.IsValid() {
return true
}
if !ObjectsAreEqual(ev.Interface(), av.Interface()) {
return true
}
}
@ -912,8 +1016,9 @@ func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{})
return Fail(t, fmt.Sprintf("%q is a subset of %q", subset, list), msgAndArgs...)
}
for i := 0; i < subsetValue.Len(); i++ {
element := subsetValue.Index(i).Interface()
subsetList := reflect.ValueOf(subset)
for i := 0; i < subsetList.Len(); i++ {
element := subsetList.Index(i).Interface()
ok, found := containsElement(list, element)
if !ok {
return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", list), msgAndArgs...)
@ -1754,6 +1859,89 @@ func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick t
}
}
// CollectT implements the TestingT interface and collects all errors.
type CollectT struct {
errors []error
}
// Errorf collects the error.
func (c *CollectT) Errorf(format string, args ...interface{}) {
c.errors = append(c.errors, fmt.Errorf(format, args...))
}
// FailNow panics.
func (c *CollectT) FailNow() {
panic("Assertion failed")
}
// Reset clears the collected errors.
func (c *CollectT) Reset() {
c.errors = nil
}
// Copy copies the collected errors to the supplied t.
func (c *CollectT) Copy(t TestingT) {
if tt, ok := t.(tHelper); ok {
tt.Helper()
}
for _, err := range c.errors {
t.Errorf("%v", err)
}
}
// EventuallyWithT asserts that given condition will be met in waitFor time,
// periodically checking target function each tick. In contrast to Eventually,
// it supplies a CollectT to the condition function, so that the condition
// function can use the CollectT to call other assertions.
// The condition is considered "met" if no errors are raised in a tick.
// The supplied CollectT collects all errors from one tick (if there are any).
// If the condition is not met before waitFor, the collected errors of
// the last tick are copied to t.
//
// externalValue := false
// go func() {
// time.Sleep(8*time.Second)
// externalValue = true
// }()
// assert.EventuallyWithT(t, func(c *assert.CollectT) {
// // add assertions as needed; any assertion failure will fail the current tick
// assert.True(c, externalValue, "expected 'externalValue' to be true")
// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false")
func EventuallyWithT(t TestingT, condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
collect := new(CollectT)
ch := make(chan bool, 1)
timer := time.NewTimer(waitFor)
defer timer.Stop()
ticker := time.NewTicker(tick)
defer ticker.Stop()
for tick := ticker.C; ; {
select {
case <-timer.C:
collect.Copy(t)
return Fail(t, "Condition never satisfied", msgAndArgs...)
case <-tick:
tick = nil
collect.Reset()
go func() {
condition(collect)
ch <- len(collect.errors) == 0
}()
case v := <-ch:
if v {
return true
}
tick = ticker.C
}
}
}
// Never asserts that the given condition doesn't satisfy in waitFor time,
// periodically checking the target function each tick.
//

View File

@ -9,6 +9,7 @@ import (
"io"
"math"
"os"
"path/filepath"
"reflect"
"regexp"
"runtime"
@ -149,6 +150,278 @@ func TestObjectsAreEqual(t *testing.T) {
}
type Nested struct {
Exported interface{}
notExported interface{}
}
type S struct {
Exported1 interface{}
Exported2 Nested
notExported1 interface{}
notExported2 Nested
}
type S2 struct {
foo interface{}
}
type S3 struct {
Exported1 *Nested
Exported2 *Nested
}
type S4 struct {
Exported1 []*Nested
}
type S5 struct {
Exported Nested
}
type S6 struct {
Exported string
unexported string
}
func TestObjectsExportedFieldsAreEqual(t *testing.T) {
intValue := 1
cases := []struct {
expected interface{}
actual interface{}
result bool
}{
{S{1, Nested{2, 3}, 4, Nested{5, 6}}, S{1, Nested{2, 3}, 4, Nested{5, 6}}, true},
{S{1, Nested{2, 3}, 4, Nested{5, 6}}, S{1, Nested{2, 3}, "a", Nested{5, 6}}, true},
{S{1, Nested{2, 3}, 4, Nested{5, 6}}, S{1, Nested{2, 3}, 4, Nested{5, "a"}}, true},
{S{1, Nested{2, 3}, 4, Nested{5, 6}}, S{1, Nested{2, 3}, 4, Nested{"a", "a"}}, true},
{S{1, Nested{2, 3}, 4, Nested{5, 6}}, S{1, Nested{2, "a"}, 4, Nested{5, 6}}, true},
{S{1, Nested{2, 3}, 4, Nested{5, 6}}, S{"a", Nested{2, 3}, 4, Nested{5, 6}}, false},
{S{1, Nested{2, 3}, 4, Nested{5, 6}}, S{1, Nested{"a", 3}, 4, Nested{5, 6}}, false},
{S{1, Nested{2, 3}, 4, Nested{5, 6}}, S2{1}, false},
{1, S{1, Nested{2, 3}, 4, Nested{5, 6}}, false},
{S3{&Nested{1, 2}, &Nested{3, 4}}, S3{&Nested{1, 2}, &Nested{3, 4}}, true},
{S3{nil, &Nested{3, 4}}, S3{nil, &Nested{3, 4}}, true},
{S3{&Nested{1, 2}, &Nested{3, 4}}, S3{&Nested{1, 2}, &Nested{3, "b"}}, true},
{S3{&Nested{1, 2}, &Nested{3, 4}}, S3{&Nested{1, "a"}, &Nested{3, "b"}}, true},
{S3{&Nested{1, 2}, &Nested{3, 4}}, S3{&Nested{"a", 2}, &Nested{3, 4}}, false},
{S3{&Nested{1, 2}, &Nested{3, 4}}, S3{}, false},
{S3{}, S3{}, true},
{S4{[]*Nested{{1, 2}}}, S4{[]*Nested{{1, 2}}}, true},
{S4{[]*Nested{{1, 2}}}, S4{[]*Nested{{1, 3}}}, true},
{S4{[]*Nested{{1, 2}, {3, 4}}}, S4{[]*Nested{{1, "a"}, {3, "b"}}}, true},
{S4{[]*Nested{{1, 2}, {3, 4}}}, S4{[]*Nested{{1, "a"}, {2, "b"}}}, false},
{Nested{&intValue, 2}, Nested{&intValue, 2}, true},
{Nested{&Nested{1, 2}, 3}, Nested{&Nested{1, "b"}, 3}, true},
{Nested{&Nested{1, 2}, 3}, Nested{nil, 3}, false},
{
Nested{map[interface{}]*Nested{nil: nil}, 2},
Nested{map[interface{}]*Nested{nil: nil}, 2},
true,
},
{
Nested{map[interface{}]*Nested{"a": nil}, 2},
Nested{map[interface{}]*Nested{"a": nil}, 2},
true,
},
{
Nested{map[interface{}]*Nested{"a": nil}, 2},
Nested{map[interface{}]*Nested{"a": {1, 2}}, 2},
false,
},
{
Nested{map[interface{}]Nested{"a": {1, 2}, "b": {3, 4}}, 2},
Nested{map[interface{}]Nested{"a": {1, 5}, "b": {3, 7}}, 2},
true,
},
{
Nested{map[interface{}]Nested{"a": {1, 2}, "b": {3, 4}}, 2},
Nested{map[interface{}]Nested{"a": {2, 2}, "b": {3, 4}}, 2},
false,
},
}
for _, c := range cases {
t.Run(fmt.Sprintf("ObjectsExportedFieldsAreEqual(%#v, %#v)", c.expected, c.actual), func(t *testing.T) {
res := ObjectsExportedFieldsAreEqual(c.expected, c.actual)
if res != c.result {
t.Errorf("ObjectsExportedFieldsAreEqual(%#v, %#v) should return %#v", c.expected, c.actual, c.result)
}
})
}
}
func TestCopyExportedFields(t *testing.T) {
intValue := 1
cases := []struct {
input interface{}
expected interface{}
}{
{
input: Nested{"a", "b"},
expected: Nested{"a", nil},
},
{
input: Nested{&intValue, 2},
expected: Nested{&intValue, nil},
},
{
input: Nested{nil, 3},
expected: Nested{nil, nil},
},
{
input: S{1, Nested{2, 3}, 4, Nested{5, 6}},
expected: S{1, Nested{2, nil}, nil, Nested{}},
},
{
input: S3{},
expected: S3{},
},
{
input: S3{&Nested{1, 2}, &Nested{3, 4}},
expected: S3{&Nested{1, nil}, &Nested{3, nil}},
},
{
input: S3{Exported1: &Nested{"a", "b"}},
expected: S3{Exported1: &Nested{"a", nil}},
},
{
input: S4{[]*Nested{
nil,
{1, 2},
}},
expected: S4{[]*Nested{
nil,
{1, nil},
}},
},
{
input: S4{[]*Nested{
{1, 2}},
},
expected: S4{[]*Nested{
{1, nil}},
},
},
{
input: S4{[]*Nested{
{1, 2},
{3, 4},
}},
expected: S4{[]*Nested{
{1, nil},
{3, nil},
}},
},
{
input: S5{Exported: Nested{"a", "b"}},
expected: S5{Exported: Nested{"a", nil}},
},
{
input: S6{"a", "b"},
expected: S6{"a", ""},
},
}
for _, c := range cases {
t.Run("", func(t *testing.T) {
output := copyExportedFields(c.input)
if !ObjectsAreEqualValues(c.expected, output) {
t.Errorf("%#v, %#v should be equal", c.expected, output)
}
})
}
}
func TestEqualExportedValues(t *testing.T) {
cases := []struct {
value1 interface{}
value2 interface{}
expectedEqual bool
expectedFail string
}{
{
value1: S{1, Nested{2, 3}, 4, Nested{5, 6}},
value2: S{1, Nested{2, nil}, nil, Nested{}},
expectedEqual: true,
},
{
value1: S{1, Nested{2, 3}, 4, Nested{5, 6}},
value2: S{1, Nested{1, nil}, nil, Nested{}},
expectedEqual: false,
expectedFail: `
Diff:
--- Expected
+++ Actual
@@ -3,3 +3,3 @@
Exported2: (assert.Nested) {
- Exported: (int) 2,
+ Exported: (int) 1,
notExported: (interface {}) <nil>`,
},
{
value1: S3{&Nested{1, 2}, &Nested{3, 4}},
value2: S3{&Nested{"a", 2}, &Nested{3, 4}},
expectedEqual: false,
expectedFail: `
Diff:
--- Expected
+++ Actual
@@ -2,3 +2,3 @@
Exported1: (*assert.Nested)({
- Exported: (int) 1,
+ Exported: (string) (len=1) "a",
notExported: (interface {}) <nil>`,
},
{
value1: S4{[]*Nested{
{1, 2},
{3, 4},
}},
value2: S4{[]*Nested{
{1, "a"},
{2, "b"},
}},
expectedEqual: false,
expectedFail: `
Diff:
--- Expected
+++ Actual
@@ -7,3 +7,3 @@
(*assert.Nested)({
- Exported: (int) 3,
+ Exported: (int) 2,
notExported: (interface {}) <nil>`,
},
}
for _, c := range cases {
t.Run("", func(t *testing.T) {
mockT := new(mockTestingT)
actual := EqualExportedValues(mockT, c.value1, c.value2)
if actual != c.expectedEqual {
t.Errorf("Expected EqualExportedValues to be %t, but was %t", c.expectedEqual, actual)
}
actualFail := mockT.errorString()
if !strings.Contains(actualFail, c.expectedFail) {
t.Errorf("Contains failure should include %q but was %q", c.expectedFail, actualFail)
}
})
}
}
func TestImplements(t *testing.T) {
mockT := new(testing.T)
@ -643,15 +916,59 @@ func TestContainsNotContains(t *testing.T) {
}
}
func TestContainsFailMessage(t *testing.T) {
func TestContainsNotContainsFailMessage(t *testing.T) {
mockT := new(mockTestingT)
Contains(mockT, "Hello World", errors.New("Hello"))
expectedFail := "\"Hello World\" does not contain &errors.errorString{s:\"Hello\"}"
type nonContainer struct {
Value string
}
cases := []struct {
assertion func(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool
container interface{}
instance interface{}
expected string
}{
{
assertion: Contains,
container: "Hello World",
instance: errors.New("Hello"),
expected: "\"Hello World\" does not contain &errors.errorString{s:\"Hello\"}",
},
{
assertion: Contains,
container: map[string]int{"one": 1},
instance: "two",
expected: "map[string]int{\"one\":1} does not contain \"two\"\n",
},
{
assertion: NotContains,
container: map[string]int{"one": 1},
instance: "one",
expected: "map[string]int{\"one\":1} should not contain \"one\"",
},
{
assertion: Contains,
container: nonContainer{Value: "Hello"},
instance: "Hello",
expected: "assert.nonContainer{Value:\"Hello\"} could not be applied builtin len()\n",
},
{
assertion: NotContains,
container: nonContainer{Value: "Hello"},
instance: "Hello",
expected: "assert.nonContainer{Value:\"Hello\"} could not be applied builtin len()\n",
},
}
for _, c := range cases {
name := filepath.Base(runtime.FuncForPC(reflect.ValueOf(c.assertion).Pointer()).Name())
t.Run(fmt.Sprintf("%v(%T, %T)", name, c.container, c.instance), func(t *testing.T) {
c.assertion(mockT, c.container, c.instance)
actualFail := mockT.errorString()
if !strings.Contains(actualFail, expectedFail) {
t.Errorf("Contains failure should include %q but was %q", expectedFail, actualFail)
if !strings.Contains(actualFail, c.expected) {
t.Errorf("Contains failure should include %q but was %q", c.expected, actualFail)
}
})
}
}
@ -672,20 +989,18 @@ func TestContainsNotContainsOnNilValue(t *testing.T) {
}
func TestSubsetNotSubset(t *testing.T) {
// MTestCase adds a custom message to the case
cases := []struct {
expected interface{}
actual interface{}
list interface{}
subset interface{}
result bool
message string
}{
// cases that are expected to contain
{[]int{1, 2, 3}, nil, true, "given subset is nil"},
{[]int{1, 2, 3}, []int{}, true, "any set contains the nil set"},
{[]int{1, 2, 3}, []int{1, 2}, true, "[1, 2, 3] contains [1, 2]"},
{[]int{1, 2, 3}, []int{1, 2, 3}, true, "[1, 2, 3] contains [1, 2, 3"},
{[]string{"hello", "world"}, []string{"hello"}, true, "[\"hello\", \"world\"] contains [\"hello\"]"},
{[]int{1, 2, 3}, nil, true, `nil is the empty set which is a subset of every set`},
{[]int{1, 2, 3}, []int{}, true, `[] is a subset of ['\x01' '\x02' '\x03']`},
{[]int{1, 2, 3}, []int{1, 2}, true, `['\x01' '\x02'] is a subset of ['\x01' '\x02' '\x03']`},
{[]int{1, 2, 3}, []int{1, 2, 3}, true, `['\x01' '\x02' '\x03'] is a subset of ['\x01' '\x02' '\x03']`},
{[]string{"hello", "world"}, []string{"hello"}, true, `["hello"] is a subset of ["hello" "world"]`},
{map[string]string{
"a": "x",
"c": "z",
@ -693,12 +1008,12 @@ func TestSubsetNotSubset(t *testing.T) {
}, map[string]string{
"a": "x",
"b": "y",
}, true, `{ "a": "x", "b": "y", "c": "z"} contains { "a": "x", "b": "y"}`},
}, true, `map["a":"x" "b":"y"] is a subset of map["a":"x" "b":"y" "c":"z"]`},
// cases that are expected not to contain
{[]string{"hello", "world"}, []string{"hello", "testify"}, false, "[\"hello\", \"world\"] does not contain [\"hello\", \"testify\"]"},
{[]int{1, 2, 3}, []int{4, 5}, false, "[1, 2, 3] does not contain [4, 5"},
{[]int{1, 2, 3}, []int{1, 5}, false, "[1, 2, 3] does not contain [1, 5]"},
{[]string{"hello", "world"}, []string{"hello", "testify"}, false, `[]string{"hello", "world"} does not contain "testify"`},
{[]int{1, 2, 3}, []int{4, 5}, false, `[]int{1, 2, 3} does not contain 4`},
{[]int{1, 2, 3}, []int{1, 5}, false, `[]int{1, 2, 3} does not contain 5`},
{map[string]string{
"a": "x",
"c": "z",
@ -706,35 +1021,51 @@ func TestSubsetNotSubset(t *testing.T) {
}, map[string]string{
"a": "x",
"b": "z",
}, false, `{ "a": "x", "b": "y", "c": "z"} does not contain { "a": "x", "b": "z"}`},
}, false, `map[string]string{"a":"x", "b":"y", "c":"z"} does not contain map[string]string{"a":"x", "b":"z"}`},
{map[string]string{
"a": "x",
"b": "y",
}, map[string]string{
"a": "x",
"b": "y",
"c": "z",
}, false, `map[string]string{"a":"x", "b":"y"} does not contain map[string]string{"a":"x", "b":"y", "c":"z"}`},
}
for _, c := range cases {
t.Run("SubSet: "+c.message, func(t *testing.T) {
mockT := new(testing.T)
res := Subset(mockT, c.expected, c.actual)
mockT := new(mockTestingT)
res := Subset(mockT, c.list, c.subset)
if res != c.result {
if res {
t.Errorf("Subset should return true: %s", c.message)
} else {
t.Errorf("Subset should return false: %s", c.message)
t.Errorf("Subset should return %t: %s", c.result, c.message)
}
if !c.result {
expectedFail := c.message
actualFail := mockT.errorString()
if !strings.Contains(actualFail, expectedFail) {
t.Log(actualFail)
t.Errorf("Subset failure should contain %q but was %q", expectedFail, actualFail)
}
}
})
}
for _, c := range cases {
t.Run("NotSubSet: "+c.message, func(t *testing.T) {
mockT := new(testing.T)
res := NotSubset(mockT, c.expected, c.actual)
mockT := new(mockTestingT)
res := NotSubset(mockT, c.list, c.subset)
// NotSubset should match the inverse of Subset. If it doesn't, something is wrong
if res == Subset(mockT, c.expected, c.actual) {
if res {
t.Errorf("NotSubset should return true: %s", c.message)
} else {
t.Errorf("NotSubset should return false: %s", c.message)
if res == Subset(mockT, c.list, c.subset) {
t.Errorf("NotSubset should return %t: %s", !c.result, c.message)
}
if c.result {
expectedFail := c.message
actualFail := mockT.errorString()
if !strings.Contains(actualFail, expectedFail) {
t.Log(actualFail)
t.Errorf("NotSubset failure should contain %q but was %q", expectedFail, actualFail)
}
}
})
@ -2429,6 +2760,32 @@ func TestEventuallyTrue(t *testing.T) {
True(t, Eventually(t, condition, 100*time.Millisecond, 20*time.Millisecond))
}
func TestEventuallyWithTFalse(t *testing.T) {
mockT := new(CollectT)
condition := func(collect *CollectT) {
True(collect, false)
}
False(t, EventuallyWithT(mockT, condition, 100*time.Millisecond, 20*time.Millisecond))
Len(t, mockT.errors, 2)
}
func TestEventuallyWithTTrue(t *testing.T) {
mockT := new(CollectT)
state := 0
condition := func(collect *CollectT) {
defer func() {
state += 1
}()
True(collect, state == 2)
}
True(t, EventuallyWithT(mockT, condition, 100*time.Millisecond, 20*time.Millisecond))
Len(t, mockT.errors, 0)
}
func TestNeverFalse(t *testing.T) {
condition := func() bool {
return false

View File

@ -1,8 +1,9 @@
// Package assert provides a set of comprehensive testing tools for use with the normal Go testing system.
//
// Example Usage
// # Example Usage
//
// The following is a complete example using assert in a standard test function:
//
// import (
// "testing"
// "github.com/stretchr/testify/assert"
@ -33,7 +34,7 @@
// assert.Equal(a, b, "The two words should be the same.")
// }
//
// Assertions
// # Assertions
//
// Assertions allow you to easily write test code, and are global funcs in the `assert` package.
// All assertion functions take, as the first argument, the `*testing.T` object provided by the

4
go.mod
View File

@ -1,6 +1,8 @@
module github.com/stretchr/testify
go 1.13
// This should match the minimum supported version that is tested in
// .github/workflows/main.yml
go 1.19
require (
github.com/davecgh/go-spew v1.1.1

View File

@ -1,7 +1,7 @@
// Package mock provides a system by which it is possible to mock your objects
// and verify calls are happening as expected.
//
// Example Usage
// # Example Usage
//
// The mock package provides an object, Mock, that tracks activity on another object. It is usually
// embedded into a test object as shown below:

View File

@ -3,6 +3,7 @@ package mock
import (
"errors"
"fmt"
"path"
"reflect"
"regexp"
"runtime"
@ -13,6 +14,7 @@ import (
"github.com/davecgh/go-spew/spew"
"github.com/pmezard/go-difflib/difflib"
"github.com/stretchr/objx"
"github.com/stretchr/testify/assert"
)
@ -109,7 +111,7 @@ func (c *Call) Return(returnArguments ...interface{}) *Call {
return c
}
// Panic specifies if the functon call should fail and the panic message
// Panic specifies if the function call should fail and the panic message
//
// Mock.On("DoSomething").Panic("test panic")
func (c *Call) Panic(msg string) *Call {
@ -121,21 +123,21 @@ func (c *Call) Panic(msg string) *Call {
return c
}
// Once indicates that that the mock should only return the value once.
// Once indicates that the mock should only return the value once.
//
// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Once()
func (c *Call) Once() *Call {
return c.Times(1)
}
// Twice indicates that that the mock should only return the value twice.
// Twice indicates that the mock should only return the value twice.
//
// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Twice()
func (c *Call) Twice() *Call {
return c.Times(2)
}
// Times indicates that that the mock should only return the indicated number
// Times indicates that the mock should only return the indicated number
// of times.
//
// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Times(5)
@ -197,12 +199,14 @@ func (c *Call) Maybe() *Call {
// Mock.
// On("MyMethod", 1).Return(nil).
// On("MyOtherMethod", 'a', 'b', 'c').Return(errors.New("Some Error"))
//
//go:noinline
func (c *Call) On(methodName string, arguments ...interface{}) *Call {
return c.Parent.On(methodName, arguments...)
}
// Unset removes a mock handler from being called.
//
// test.On("func", mock.Anything).Unset()
func (c *Call) Unset() *Call {
var unlockOnce sync.Once
@ -424,6 +428,10 @@ func callString(method string, arguments Arguments, includeArgumentValues bool)
if includeArgumentValues {
var argVals []string
for argIndex, arg := range arguments {
if _, ok := arg.(*FunctionalOptionsArgument); ok {
argVals = append(argVals, fmt.Sprintf("%d: %s", argIndex, arg))
continue
}
argVals = append(argVals, fmt.Sprintf("%d: %#v", argIndex, arg))
}
argValsString = fmt.Sprintf("\n\t\t%s", strings.Join(argVals, "\n\t\t"))
@ -466,7 +474,7 @@ func (m *Mock) MethodCalled(methodName string, arguments ...interface{}) Argumen
found, call := m.findExpectedCall(methodName, arguments...)
if found < 0 {
// expected call found but it has already been called with repeatable times
// expected call found, but it has already been called with repeatable times
if call != nil {
m.mutex.Unlock()
m.fail("\nassert: mock: The method has been called over %d times.\n\tEither do one more Mock.On(\"%s\").Return(...), or remove extra call.\n\tThis call was unexpected:\n\t\t%s\n\tat: %s", call.totalCalls, methodName, callString(methodName, arguments, true), assert.CallerInfo())
@ -555,7 +563,7 @@ func (m *Mock) MethodCalled(methodName string, arguments ...interface{}) Argumen
Assertions
*/
type assertExpectationser interface {
type assertExpectationiser interface {
AssertExpectations(TestingT) bool
}
@ -572,7 +580,7 @@ func AssertExpectationsForObjects(t TestingT, testObjects ...interface{}) bool {
t.Logf("Deprecated mock.AssertExpectationsForObjects(myMock.Mock) use mock.AssertExpectationsForObjects(myMock)")
obj = m
}
m := obj.(assertExpectationser)
m := obj.(assertExpectationiser)
if !m.AssertExpectations(t) {
t.Logf("Expectations didn't match for Mock: %+v", reflect.TypeOf(m))
return false
@ -758,6 +766,7 @@ type AnythingOfTypeArgument string
// name of the type to check for. Used in Diff and Assert.
//
// For example:
//
// Assert(t, AnythingOfType("string"), AnythingOfType("int"))
func AnythingOfType(t string) AnythingOfTypeArgument {
return AnythingOfTypeArgument(t)
@ -780,6 +789,34 @@ func IsType(t interface{}) *IsTypeArgument {
return &IsTypeArgument{t: t}
}
// FunctionalOptionsArgument is a struct that contains the type and value of an functional option argument
// for use when type checking.
type FunctionalOptionsArgument struct {
value interface{}
}
// String returns the string representation of FunctionalOptionsArgument
func (f *FunctionalOptionsArgument) String() string {
var name string
tValue := reflect.ValueOf(f.value)
if tValue.Len() > 0 {
name = "[]" + reflect.TypeOf(tValue.Index(0).Interface()).String()
}
return strings.Replace(fmt.Sprintf("%#v", f.value), "[]interface {}", name, 1)
}
// FunctionalOptions returns an FunctionalOptionsArgument object containing the functional option type
// and the values to check of
//
// For example:
// Assert(t, FunctionalOptions("[]foo.FunctionalOption", foo.Opt1(), foo.Opt2()))
func FunctionalOptions(value ...interface{}) *FunctionalOptionsArgument {
return &FunctionalOptionsArgument{
value: value,
}
}
// argumentMatcher performs custom argument matching, returning whether or
// not the argument is matched by the expectation fixture function.
type argumentMatcher struct {
@ -926,6 +963,29 @@ func (args Arguments) Diff(objects []interface{}) (string, int) {
differences++
output = fmt.Sprintf("%s\t%d: FAIL: type %s != type %s - %s\n", output, i, reflect.TypeOf(t).Name(), reflect.TypeOf(actual).Name(), actualFmt)
}
} else if reflect.TypeOf(expected) == reflect.TypeOf((*FunctionalOptionsArgument)(nil)) {
t := expected.(*FunctionalOptionsArgument).value
var name string
tValue := reflect.ValueOf(t)
if tValue.Len() > 0 {
name = "[]" + reflect.TypeOf(tValue.Index(0).Interface()).String()
}
tName := reflect.TypeOf(t).Name()
if name != reflect.TypeOf(actual).String() && tValue.Len() != 0 {
differences++
output = fmt.Sprintf("%s\t%d: FAIL: type %s != type %s - %s\n", output, i, tName, reflect.TypeOf(actual).Name(), actualFmt)
} else {
if ef, af := assertOpts(t, actual); ef == "" && af == "" {
// match
output = fmt.Sprintf("%s\t%d: PASS: %s == %s\n", output, i, tName, tName)
} else {
// not match
differences++
output = fmt.Sprintf("%s\t%d: FAIL: %s != %s\n", output, i, af, ef)
}
}
} else {
// normal checking
@ -1102,3 +1162,65 @@ var spewConfig = spew.ConfigState{
type tHelper interface {
Helper()
}
func assertOpts(expected, actual interface{}) (expectedFmt, actualFmt string) {
expectedOpts := reflect.ValueOf(expected)
actualOpts := reflect.ValueOf(actual)
var expectedNames []string
for i := 0; i < expectedOpts.Len(); i++ {
expectedNames = append(expectedNames, funcName(expectedOpts.Index(i).Interface()))
}
var actualNames []string
for i := 0; i < actualOpts.Len(); i++ {
actualNames = append(actualNames, funcName(actualOpts.Index(i).Interface()))
}
if !assert.ObjectsAreEqual(expectedNames, actualNames) {
expectedFmt = fmt.Sprintf("%v", expectedNames)
actualFmt = fmt.Sprintf("%v", actualNames)
return
}
for i := 0; i < expectedOpts.Len(); i++ {
expectedOpt := expectedOpts.Index(i).Interface()
actualOpt := actualOpts.Index(i).Interface()
expectedFunc := expectedNames[i]
actualFunc := actualNames[i]
if expectedFunc != actualFunc {
expectedFmt = expectedFunc
actualFmt = actualFunc
return
}
ot := reflect.TypeOf(expectedOpt)
var expectedValues []reflect.Value
var actualValues []reflect.Value
if ot.NumIn() == 0 {
return
}
for i := 0; i < ot.NumIn(); i++ {
vt := ot.In(i).Elem()
expectedValues = append(expectedValues, reflect.New(vt))
actualValues = append(actualValues, reflect.New(vt))
}
reflect.ValueOf(expectedOpt).Call(expectedValues)
reflect.ValueOf(actualOpt).Call(actualValues)
for i := 0; i < ot.NumIn(); i++ {
if !assert.ObjectsAreEqual(expectedValues[i].Interface(), actualValues[i].Interface()) {
expectedFmt = fmt.Sprintf("%s %+v", expectedNames[i], expectedValues[i].Interface())
actualFmt = fmt.Sprintf("%s %+v", expectedNames[i], actualValues[i].Interface())
return
}
}
}
return "", ""
}
func funcName(opt interface{}) string {
n := runtime.FuncForPC(reflect.ValueOf(opt).Pointer()).Name()
return strings.TrimSuffix(path.Base(n), path.Ext(n))
}

View File

@ -32,6 +32,29 @@ func (i *TestExampleImplementation) TheExampleMethod(a, b, c int) (int, error) {
return args.Int(0), errors.New("Whoops")
}
type options struct {
num int
str string
}
type OptionFn func(*options)
func OpNum(n int) OptionFn {
return func(o *options) {
o.num = n
}
}
func OpStr(s string) OptionFn {
return func(o *options) {
o.str = s
}
}
func (i *TestExampleImplementation) TheExampleMethodFunctionalOptions(x string, opts ...OptionFn) error {
args := i.Called(x, opts)
return args.Error(0)
}
//go:noinline
func (i *TestExampleImplementation) TheExampleMethod2(yesorno bool) {
i.Called(yesorno)
@ -1415,6 +1438,40 @@ func Test_Mock_AssertExpectationsCustomType(t *testing.T) {
}
func Test_Mock_AssertExpectationsFunctionalOptionsType(t *testing.T) {
var mockedService = new(TestExampleImplementation)
mockedService.On("TheExampleMethodFunctionalOptions", "test", FunctionalOptions(OpNum(1), OpStr("foo"))).Return(nil).Once()
tt := new(testing.T)
assert.False(t, mockedService.AssertExpectations(tt))
// make the call now
mockedService.TheExampleMethodFunctionalOptions("test", OpNum(1), OpStr("foo"))
// now assert expectations
assert.True(t, mockedService.AssertExpectations(tt))
}
func Test_Mock_AssertExpectationsFunctionalOptionsType_Empty(t *testing.T) {
var mockedService = new(TestExampleImplementation)
mockedService.On("TheExampleMethodFunctionalOptions", "test", FunctionalOptions()).Return(nil).Once()
tt := new(testing.T)
assert.False(t, mockedService.AssertExpectations(tt))
// make the call now
mockedService.TheExampleMethodFunctionalOptions("test")
// now assert expectations
assert.True(t, mockedService.AssertExpectations(tt))
}
func Test_Mock_AssertExpectations_With_Repeatability(t *testing.T) {
var mockedService = new(TestExampleImplementation)
@ -1599,7 +1656,7 @@ func Test_Mock_AssertOptional(t *testing.T) {
}
/*
Arguments helper methods
Arguments helper methods
*/
func Test_Arguments_Get(t *testing.T) {

View File

@ -1,9 +1,10 @@
// Package require implements the same assertions as the `assert` package but
// stops test execution when a test fails.
//
// Example Usage
// # Example Usage
//
// The following is a complete example using require in a standard test function:
//
// import (
// "testing"
// "github.com/stretchr/testify/require"
@ -18,7 +19,7 @@
//
// }
//
// Assertions
// # Assertions
//
// The `require` package have same global functions as in the `assert` package,
// but instead of returning a boolean result they call `t.FailNow()`.

View File

@ -195,6 +195,46 @@ func EqualErrorf(t TestingT, theError error, errString string, msg string, args
t.FailNow()
}
// EqualExportedValues asserts that the types of two objects are equal and their public
// fields are also equal. This is useful for comparing structs that have private fields
// that could potentially differ.
//
// type S struct {
// Exported int
// notExported int
// }
// assert.EqualExportedValues(t, S{1, 2}, S{1, 3}) => true
// assert.EqualExportedValues(t, S{1, 2}, S{2, 3}) => false
func EqualExportedValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if assert.EqualExportedValues(t, expected, actual, msgAndArgs...) {
return
}
t.FailNow()
}
// EqualExportedValuesf asserts that the types of two objects are equal and their public
// fields are also equal. This is useful for comparing structs that have private fields
// that could potentially differ.
//
// type S struct {
// Exported int
// notExported int
// }
// assert.EqualExportedValuesf(t, S{1, 2}, S{1, 3}, "error message %s", "formatted") => true
// assert.EqualExportedValuesf(t, S{1, 2}, S{2, 3}, "error message %s", "formatted") => false
func EqualExportedValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if assert.EqualExportedValuesf(t, expected, actual, msg, args...) {
return
}
t.FailNow()
}
// EqualValues asserts that two objects are equal or convertable to the same types
// and equal.
//
@ -364,6 +404,62 @@ func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick t
t.FailNow()
}
// EventuallyWithT asserts that given condition will be met in waitFor time,
// periodically checking target function each tick. In contrast to Eventually,
// it supplies a CollectT to the condition function, so that the condition
// function can use the CollectT to call other assertions.
// The condition is considered "met" if no errors are raised in a tick.
// The supplied CollectT collects all errors from one tick (if there are any).
// If the condition is not met before waitFor, the collected errors of
// the last tick are copied to t.
//
// externalValue := false
// go func() {
// time.Sleep(8*time.Second)
// externalValue = true
// }()
// assert.EventuallyWithT(t, func(c *assert.CollectT) {
// // add assertions as needed; any assertion failure will fail the current tick
// assert.True(c, externalValue, "expected 'externalValue' to be true")
// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false")
func EventuallyWithT(t TestingT, condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if assert.EventuallyWithT(t, condition, waitFor, tick, msgAndArgs...) {
return
}
t.FailNow()
}
// EventuallyWithTf asserts that given condition will be met in waitFor time,
// periodically checking target function each tick. In contrast to Eventually,
// it supplies a CollectT to the condition function, so that the condition
// function can use the CollectT to call other assertions.
// The condition is considered "met" if no errors are raised in a tick.
// The supplied CollectT collects all errors from one tick (if there are any).
// If the condition is not met before waitFor, the collected errors of
// the last tick are copied to t.
//
// externalValue := false
// go func() {
// time.Sleep(8*time.Second)
// externalValue = true
// }()
// assert.EventuallyWithTf(t, func(c *assert.CollectT, "error message %s", "formatted") {
// // add assertions as needed; any assertion failure will fail the current tick
// assert.True(c, externalValue, "expected 'externalValue' to be true")
// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false")
func EventuallyWithTf(t TestingT, condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if assert.EventuallyWithTf(t, condition, waitFor, tick, msg, args...) {
return
}
t.FailNow()
}
// Eventuallyf asserts that given condition will be met in waitFor time,
// periodically checking target function each tick.
//

View File

@ -156,6 +156,40 @@ func (a *Assertions) EqualErrorf(theError error, errString string, msg string, a
EqualErrorf(a.t, theError, errString, msg, args...)
}
// EqualExportedValues asserts that the types of two objects are equal and their public
// fields are also equal. This is useful for comparing structs that have private fields
// that could potentially differ.
//
// type S struct {
// Exported int
// notExported int
// }
// a.EqualExportedValues(S{1, 2}, S{1, 3}) => true
// a.EqualExportedValues(S{1, 2}, S{2, 3}) => false
func (a *Assertions) EqualExportedValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
EqualExportedValues(a.t, expected, actual, msgAndArgs...)
}
// EqualExportedValuesf asserts that the types of two objects are equal and their public
// fields are also equal. This is useful for comparing structs that have private fields
// that could potentially differ.
//
// type S struct {
// Exported int
// notExported int
// }
// a.EqualExportedValuesf(S{1, 2}, S{1, 3}, "error message %s", "formatted") => true
// a.EqualExportedValuesf(S{1, 2}, S{2, 3}, "error message %s", "formatted") => false
func (a *Assertions) EqualExportedValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
EqualExportedValuesf(a.t, expected, actual, msg, args...)
}
// EqualValues asserts that two objects are equal or convertable to the same types
// and equal.
//
@ -289,6 +323,56 @@ func (a *Assertions) Eventually(condition func() bool, waitFor time.Duration, ti
Eventually(a.t, condition, waitFor, tick, msgAndArgs...)
}
// EventuallyWithT asserts that given condition will be met in waitFor time,
// periodically checking target function each tick. In contrast to Eventually,
// it supplies a CollectT to the condition function, so that the condition
// function can use the CollectT to call other assertions.
// The condition is considered "met" if no errors are raised in a tick.
// The supplied CollectT collects all errors from one tick (if there are any).
// If the condition is not met before waitFor, the collected errors of
// the last tick are copied to t.
//
// externalValue := false
// go func() {
// time.Sleep(8*time.Second)
// externalValue = true
// }()
// a.EventuallyWithT(func(c *assert.CollectT) {
// // add assertions as needed; any assertion failure will fail the current tick
// assert.True(c, externalValue, "expected 'externalValue' to be true")
// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false")
func (a *Assertions) EventuallyWithT(condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
EventuallyWithT(a.t, condition, waitFor, tick, msgAndArgs...)
}
// EventuallyWithTf asserts that given condition will be met in waitFor time,
// periodically checking target function each tick. In contrast to Eventually,
// it supplies a CollectT to the condition function, so that the condition
// function can use the CollectT to call other assertions.
// The condition is considered "met" if no errors are raised in a tick.
// The supplied CollectT collects all errors from one tick (if there are any).
// If the condition is not met before waitFor, the collected errors of
// the last tick are copied to t.
//
// externalValue := false
// go func() {
// time.Sleep(8*time.Second)
// externalValue = true
// }()
// a.EventuallyWithTf(func(c *assert.CollectT, "error message %s", "formatted") {
// // add assertions as needed; any assertion failure will fail the current tick
// assert.True(c, externalValue, "expected 'externalValue' to be true")
// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false")
func (a *Assertions) EventuallyWithTf(condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
EventuallyWithTf(a.t, condition, waitFor, tick, msg, args...)
}
// Eventuallyf asserts that given condition will be met in waitFor time,
// periodically checking target function each tick.
//

View File

@ -29,6 +29,7 @@
// Suite object has assertion methods.
//
// A crude example:
//
// // Basic imports
// import (
// "testing"