Compare commits

...

38 Commits

Author SHA1 Message Date
Bracken 5ac6528bff
Merge pull request #1716 from brackendawson/update-maintainers
Propose Christophe Colombier (ccoVeille) as approver
2025-03-24 18:30:37 +01:00
Bracken Dawson d0e0f4961b
Propose Christophe Colombier (ccoVeille) as approver 2025-03-23 18:36:20 +01:00
Bracken Dawson b561f16e87
Correct maintainers list
arjunmahishi is actually only an approver, at the moment.
2025-03-23 18:33:47 +01:00
Bracken a948a8c402
Merge pull request #1674 from alexandear/refactor/simplify-with-sprintf-q
refactor: use %q and %T to simplify fmt.Sprintf
2025-03-23 14:23:15 +01:00
Bracken c3915e850a
Merge branch 'master' into refactor/simplify-with-sprintf-q 2025-03-23 14:22:18 +01:00
Bracken 16020e8cbc
Merge pull request #1687 from alexandear/docs-format-go-examples
README: apply gofmt to examples
2025-03-23 12:09:57 +01:00
Oleksandr Redko 75df9d50d4 Update mdsf 2025-03-23 12:40:01 +02:00
Oleksandr Redko 33be8f984a Move to jobs.build 2025-03-23 12:20:44 +02:00
Oleksandr Redko a9e8aed155 Remove mistakenly added .ci.gofmt.sh 2025-03-23 12:18:52 +02:00
Bracken 3b8bd9bf7d
Merge pull request #1614 from DevotedHealth/mauclair-call-stack-perf
Call stack perf change for CallerInfo
2025-03-22 23:13:13 +01:00
Bracken 1e7fb5865a
Merge pull request #1709 from techfg/chore/issue-1621-update-docs
chore: update docs for Unset #1621
2025-03-22 12:44:25 +01:00
techfg c6ac9bb91d chore: update per PR feedback 2025-03-21 06:25:01 -07:00
Bracken 65f73866c0
Merge pull request #1710 from greg0ire/doc-goroutine-restriction
Document consequences of calling t.FailNow()
2025-03-21 13:27:31 +01:00
Grégoire Paris b1c9368f81
Improve existing docs 2025-03-21 13:21:25 +01:00
Bracken 5a5ac85551
Merge pull request #1062 from lambdanis/fix-fail-message-formatting
Fix failure message formatting for Positive and Negative asserts
2025-03-21 10:15:07 +01:00
Bracken 53e0c918d4
Merge branch 'master' into fix-fail-message-formatting 2025-03-21 10:10:08 +01:00
Grégoire Paris 89086b0757
Document consequences of calling t.FailNow()
These comments are adapted from t.FailNow()'s own documentation.

Closes #1701
2025-03-19 20:50:44 +01:00
techfg 098128fd10 chore: update docs for Unset #1621 2025-03-18 12:48:09 -07:00
Bracken f784abc221
Merge pull request #1345 from craig65535/fix-assert-erroras
assert.ErrorAs: log target type
2025-03-18 18:04:00 +00:00
Oleksandr Redko dfda68b86f Verify formatting of code snippets in CI 2025-01-20 19:49:35 +02:00
Oleksandr Redko 3cf0926564 docs: Format examples in README 2025-01-07 11:35:15 +01:00
Craig Davison c60c3bd7fb dereference target 2025-01-03 14:34:16 -07:00
Craig Davison 1c717c00c1 Add return 2024-12-31 15:35:31 -07:00
Craig Davison ccb5e7f656 Remove redundant returns 2024-12-31 15:26:24 -07:00
Craig Davison ca6698b8a1 assert.ErrorAs: log target type 2024-12-31 15:09:40 -07:00
Bracken 7c367bb7bc
Merge pull request #1684 from alexandear/refactor-replace-deprecated-ioutil
Replace deprecated io/ioutil with io and os
2024-12-21 13:10:41 +00:00
Bracken e6a990c21d
Merge pull request #1688 from alexandear/docs-fix-typo
docs: Fix typo in README
2024-12-21 13:09:58 +00:00
Mike Auclair cfee2346d7 review feedback 2024-12-17 18:18:56 +00:00
Oleksandr Redko f8c628e5a1 docs: Fix typo in README 2024-12-16 23:14:16 +02:00
Oleksandr Redko 014ae9a7a4 Replace deprecated io/ioutil with io and os 2024-12-10 14:21:03 +02:00
Oleksandr Redko 30f3cef5ad Apply suggestions from code review
Co-authored-by: Olivier Mengué <dolmen@cpan.org>
2024-12-10 14:08:46 +02:00
Oleksandr Redko d57bac8721 refactor: use %q to simplify fmt.Sprintf 2024-11-24 18:28:03 +02:00
Mike Auclair 7f10816c93 review feedback 2024-06-25 11:50:49 +00:00
Mike Auclair 176474a4c9 cleanup 2024-06-24 20:42:26 +00:00
Mike Auclair 28e0be5092 refill stack frame buffer after it's exhausted 2024-06-24 18:11:07 +00:00
Mike Auclair 4a90eff4ae fix 2024-06-24 16:50:27 +00:00
Mike Auclair 3ca01f4bc3 Stop querying for stack frames multiple times on CallerInfo() 2024-06-24 16:39:18 +00:00
Ania Kapuścińska 0e5b59666a Fix failure message formatting for Positive and Negative asserts 2024-04-11 17:41:38 +02:00
15 changed files with 323 additions and 204 deletions

12
.ci.readme.fmt.sh Executable file
View File

@ -0,0 +1,12 @@
#!/bin/bash
# Verify that the code snippets in README.md are formatted.
# The tool https://github.com/hougesen/mdsf is used.
if [ -n "$(mdsf verify --config .mdsf.json --log-level error README.md 2>&1)" ]; then
echo "Go code in the README.md is not formatted."
echo "Did you forget to run 'mdsf format --config .mdsf.json README.md'?"
mdsf format --config .mdsf.json README.md
git diff
exit 1
fi

View File

@ -15,8 +15,10 @@ jobs:
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go_version }}
- run: npm install -g mdsf-cli
- run: ./.ci.gogenerate.sh
- run: ./.ci.gofmt.sh
- run: ./.ci.readme.fmt.sh
- run: ./.ci.govet.sh
- run: go test -v -race ./...
test:

12
.mdsf.json Normal file
View File

@ -0,0 +1,12 @@
{
"$schema": "https://raw.githubusercontent.com/hougesen/mdsf/main/schemas/v0.8.2/mdsf.schema.json",
"format_finished_document": false,
"languages": {
"go": [
[
"gofmt",
"goimports"
]
]
}
}

View File

@ -6,5 +6,12 @@ pull requests.
* @boyan-soubachov
* @dolmen
* @MovieStoreGuy
* @arjunmahishi
* @brackendawson
## Approvers
The individuals listed below are active in the project and have the ability to approve pull
requests.
* @arjunmahishi
* @ccoVeille

187
README.md
View File

@ -20,7 +20,7 @@ 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 https://go.dev/doc/code#Testing
* Check out the API Documentation https://pkg.go.dev/github.com/stretchr/testify
* Use [testifylint](https://github.com/Antonboom/testifylint) (via [golanci-lint](https://golangci-lint.run/)) to avoid common mistakes
* Use [testifylint](https://github.com/Antonboom/testifylint) (via [golangci-lint](https://golangci-lint.run/)) to avoid common mistakes
* A little about [Test-Driven Development (TDD)](https://en.wikipedia.org/wiki/Test-driven_development)
[`assert`](https://pkg.go.dev/github.com/stretchr/testify/assert "API documentation") package
@ -38,30 +38,27 @@ See it in action:
package yours
import (
"testing"
"github.com/stretchr/testify/assert"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSomething(t *testing.T) {
// assert equality
assert.Equal(t, 123, 123, "they should be equal")
// assert equality
assert.Equal(t, 123, 123, "they should be equal")
// assert inequality
assert.NotEqual(t, 123, 456, "they should not be equal")
// assert inequality
assert.NotEqual(t, 123, 456, "they should not be equal")
// assert for nil (good for errors)
assert.Nil(t, object)
// assert for not nil (good when you expect something)
if assert.NotNil(t, object) {
// now we know that object isn't nil, we are safe to make
// further assertions without causing any errors
assert.Equal(t, "Something", object.Value)
}
// assert for nil (good for errors)
assert.Nil(t, object)
// assert for not nil (good when you expect something)
if assert.NotNil(t, object) {
// now we know that object isn't nil, we are safe to make
// further assertions without causing any errors
assert.Equal(t, "Something", object.Value)
}
}
```
@ -74,29 +71,29 @@ if you assert many times, use the below:
package yours
import (
"testing"
"github.com/stretchr/testify/assert"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSomething(t *testing.T) {
assert := assert.New(t)
assert := assert.New(t)
// assert equality
assert.Equal(123, 123, "they should be equal")
// assert equality
assert.Equal(123, 123, "they should be equal")
// assert inequality
assert.NotEqual(123, 456, "they should not be equal")
// assert inequality
assert.NotEqual(123, 456, "they should not be equal")
// assert for nil (good for errors)
assert.Nil(object)
// assert for nil (good for errors)
assert.Nil(object)
// assert for not nil (good when you expect something)
if assert.NotNil(object) {
// now we know that object isn't nil, we are safe to make
// further assertions without causing any errors
assert.Equal("Something", object.Value)
}
// assert for not nil (good when you expect something)
if assert.NotNil(object) {
// now we know that object isn't nil, we are safe to make
// further assertions without causing any errors
assert.Equal("Something", object.Value)
}
}
```
@ -120,8 +117,9 @@ An example test function that tests a piece of code that relies on an external o
package yours
import (
"testing"
"github.com/stretchr/testify/mock"
"testing"
"github.com/stretchr/testify/mock"
)
/*
@ -130,8 +128,8 @@ import (
// MyMockedObject is a mocked object that implements an interface
// that describes an object that the code I am testing relies on.
type MyMockedObject struct{
mock.Mock
type MyMockedObject struct {
mock.Mock
}
// DoSomething is a method on MyMockedObject that implements some interface
@ -142,10 +140,8 @@ type MyMockedObject struct{
//
// NOTE: This method is not being tested here, code that uses this object is.
func (m *MyMockedObject) DoSomething(number int) (bool, error) {
args := m.Called(number)
return args.Bool(0), args.Error(1)
args := m.Called(number)
return args.Bool(0), args.Error(1)
}
/*
@ -155,20 +151,17 @@ func (m *MyMockedObject) DoSomething(number int) (bool, error) {
// TestSomething is an example of how to use our test object to
// make assertions about some target code we are testing.
func TestSomething(t *testing.T) {
// create an instance of our test object
testObj := new(MyMockedObject)
// create an instance of our test object
testObj := new(MyMockedObject)
// set up expectations
testObj.On("DoSomething", 123).Return(true, nil)
// call the code we are testing
targetFuncThatDoesSomethingWithObj(testObj)
// assert that the expectations were met
testObj.AssertExpectations(t)
// set up expectations
testObj.On("DoSomething", 123).Return(true, nil)
// call the code we are testing
targetFuncThatDoesSomethingWithObj(testObj)
// assert that the expectations were met
testObj.AssertExpectations(t)
}
// TestSomethingWithPlaceholder is a second example of how to use our test object to
@ -177,45 +170,42 @@ func TestSomething(t *testing.T) {
// data being passed in is normally dynamically generated and cannot be
// predicted beforehand (eg. containing hashes that are time sensitive)
func TestSomethingWithPlaceholder(t *testing.T) {
// create an instance of our test object
testObj := new(MyMockedObject)
// create an instance of our test object
testObj := new(MyMockedObject)
// set up expectations with a placeholder in the argument list
testObj.On("DoSomething", mock.Anything).Return(true, nil)
// set up expectations with a placeholder in the argument list
testObj.On("DoSomething", mock.Anything).Return(true, nil)
// call the code we are testing
targetFuncThatDoesSomethingWithObj(testObj)
// assert that the expectations were met
testObj.AssertExpectations(t)
// call the code we are testing
targetFuncThatDoesSomethingWithObj(testObj)
// assert that the expectations were met
testObj.AssertExpectations(t)
}
// TestSomethingElse2 is a third example that shows how you can use
// the Unset method to cleanup handlers and then add new ones.
func TestSomethingElse2(t *testing.T) {
// create an instance of our test object
testObj := new(MyMockedObject)
// create an instance of our test object
testObj := new(MyMockedObject)
// set up expectations with a placeholder in the argument list
mockCall := testObj.On("DoSomething", mock.Anything).Return(true, nil)
// set up expectations with a placeholder in the argument list
mockCall := testObj.On("DoSomething", mock.Anything).Return(true, nil)
// call the code we are testing
targetFuncThatDoesSomethingWithObj(testObj)
// call the code we are testing
targetFuncThatDoesSomethingWithObj(testObj)
// assert that the expectations were met
testObj.AssertExpectations(t)
// assert that the expectations were met
testObj.AssertExpectations(t)
// remove the handler now so we can add another one that takes precedence
mockCall.Unset()
// remove the handler now so we can add another one that takes precedence
mockCall.Unset()
// return false now instead of true
testObj.On("DoSomething", mock.Anything).Return(false, nil)
// return false now instead of true
testObj.On("DoSomething", mock.Anything).Return(false, nil)
testObj.AssertExpectations(t)
testObj.AssertExpectations(t)
}
```
@ -235,35 +225,36 @@ An example suite is shown below:
```go
// Basic imports
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
// Define the suite, and absorb the built-in basic suite
// functionality from testify - including a T() method which
// returns the current testing context
type ExampleTestSuite struct {
suite.Suite
VariableThatShouldStartAtFive int
suite.Suite
VariableThatShouldStartAtFive int
}
// Make sure that VariableThatShouldStartAtFive is set to five
// before each test
func (suite *ExampleTestSuite) SetupTest() {
suite.VariableThatShouldStartAtFive = 5
suite.VariableThatShouldStartAtFive = 5
}
// All methods that begin with "Test" are run as tests within a
// suite.
func (suite *ExampleTestSuite) TestExample() {
assert.Equal(suite.T(), 5, suite.VariableThatShouldStartAtFive)
assert.Equal(suite.T(), 5, suite.VariableThatShouldStartAtFive)
}
// In order for 'go test' to run this suite, we need to create
// a normal test function and pass our suite to suite.Run
func TestExampleTestSuite(t *testing.T) {
suite.Run(t, new(ExampleTestSuite))
suite.Run(t, new(ExampleTestSuite))
}
```
@ -276,33 +267,34 @@ For more information on writing suites, check out the [API documentation for the
```go
// Basic imports
import (
"testing"
"github.com/stretchr/testify/suite"
"testing"
"github.com/stretchr/testify/suite"
)
// Define the suite, and absorb the built-in basic suite
// functionality from testify - including assertion methods.
type ExampleTestSuite struct {
suite.Suite
VariableThatShouldStartAtFive int
suite.Suite
VariableThatShouldStartAtFive int
}
// Make sure that VariableThatShouldStartAtFive is set to five
// before each test
func (suite *ExampleTestSuite) SetupTest() {
suite.VariableThatShouldStartAtFive = 5
suite.VariableThatShouldStartAtFive = 5
}
// All methods that begin with "Test" are run as tests within a
// suite.
func (suite *ExampleTestSuite) TestExample() {
suite.Equal(suite.VariableThatShouldStartAtFive, 5)
suite.Equal(suite.VariableThatShouldStartAtFive, 5)
}
// In order for 'go test' to run this suite, we need to create
// a normal test function and pass our suite to suite.Run
func TestExampleTestSuite(t *testing.T) {
suite.Run(t, new(ExampleTestSuite))
suite.Run(t, new(ExampleTestSuite))
}
```
@ -329,14 +321,13 @@ Import the `testify/assert` package into your code using this template:
package yours
import (
"testing"
"github.com/stretchr/testify/assert"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSomething(t *testing.T) {
assert.True(t, true, "True is true!")
assert.True(t, true, "True is true!")
}
```

View File

@ -16,7 +16,6 @@ import (
"go/token"
"go/types"
"io"
"io/ioutil"
"log"
"os"
"path"
@ -101,7 +100,7 @@ func parseTemplates() (*template.Template, *template.Template, error) {
return nil, nil, err
}
if *tmplFile != "" {
f, err := ioutil.ReadFile(*tmplFile)
f, err := os.ReadFile(*tmplFile)
if err != nil {
return nil, nil, err
}
@ -183,7 +182,7 @@ func parsePackageSource(pkg string) (*types.Scope, *doc.Package, error) {
files := make(map[string]*ast.File)
fileList := make([]*ast.File, len(pd.GoFiles))
for i, fname := range pd.GoFiles {
src, err := ioutil.ReadFile(path.Join(pd.Dir, fname))
src, err := os.ReadFile(path.Join(pd.Dir, fname))
if err != nil {
return nil, nil, err
}

View File

@ -390,7 +390,8 @@ func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface
if h, ok := t.(tHelper); ok {
h.Helper()
}
return compareTwoValues(t, e1, e2, []compareResult{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs...)
failMessage := fmt.Sprintf("\"%v\" is not greater than \"%v\"", e1, e2)
return compareTwoValues(t, e1, e2, []compareResult{compareGreater}, failMessage, msgAndArgs...)
}
// GreaterOrEqual asserts that the first element is greater than or equal to the second
@ -403,7 +404,8 @@ func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...in
if h, ok := t.(tHelper); ok {
h.Helper()
}
return compareTwoValues(t, e1, e2, []compareResult{compareGreater, compareEqual}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs...)
failMessage := fmt.Sprintf("\"%v\" is not greater than or equal to \"%v\"", e1, e2)
return compareTwoValues(t, e1, e2, []compareResult{compareGreater, compareEqual}, failMessage, msgAndArgs...)
}
// Less asserts that the first element is less than the second
@ -415,7 +417,8 @@ func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{})
if h, ok := t.(tHelper); ok {
h.Helper()
}
return compareTwoValues(t, e1, e2, []compareResult{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs...)
failMessage := fmt.Sprintf("\"%v\" is not less than \"%v\"", e1, e2)
return compareTwoValues(t, e1, e2, []compareResult{compareLess}, failMessage, msgAndArgs...)
}
// LessOrEqual asserts that the first element is less than or equal to the second
@ -428,7 +431,8 @@ func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...inter
if h, ok := t.(tHelper); ok {
h.Helper()
}
return compareTwoValues(t, e1, e2, []compareResult{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs...)
failMessage := fmt.Sprintf("\"%v\" is not less than or equal to \"%v\"", e1, e2)
return compareTwoValues(t, e1, e2, []compareResult{compareLess, compareEqual}, failMessage, msgAndArgs...)
}
// Positive asserts that the specified element is positive
@ -440,7 +444,8 @@ func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
h.Helper()
}
zero := reflect.Zero(reflect.TypeOf(e))
return compareTwoValues(t, e, zero.Interface(), []compareResult{compareGreater}, "\"%v\" is not positive", msgAndArgs...)
failMessage := fmt.Sprintf("\"%v\" is not positive", e)
return compareTwoValues(t, e, zero.Interface(), []compareResult{compareGreater}, failMessage, msgAndArgs...)
}
// Negative asserts that the specified element is negative
@ -452,7 +457,8 @@ func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
h.Helper()
}
zero := reflect.Zero(reflect.TypeOf(e))
return compareTwoValues(t, e, zero.Interface(), []compareResult{compareLess}, "\"%v\" is not negative", msgAndArgs...)
failMessage := fmt.Sprintf("\"%v\" is not negative", e)
return compareTwoValues(t, e, zero.Interface(), []compareResult{compareLess}, failMessage, msgAndArgs...)
}
func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedComparesResults []compareResult, failMessage string, msgAndArgs ...interface{}) bool {
@ -468,11 +474,11 @@ func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedCompare
compareResult, isComparable := compare(e1, e2, e1Kind)
if !isComparable {
return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...)
return Fail(t, fmt.Sprintf(`Can not compare type "%T"`, e1), msgAndArgs...)
}
if !containsValue(allowedComparesResults, compareResult) {
return Fail(t, fmt.Sprintf(failMessage, e1, e2), msgAndArgs...)
return Fail(t, failMessage, msgAndArgs...)
}
return true

View File

@ -33,7 +33,7 @@ func isOrdered(t TestingT, object interface{}, allowedComparesResults []compareR
compareResult, isComparable := compare(prevValueInterface, valueInterface, firstValueKind)
if !isComparable {
return Fail(t, fmt.Sprintf("Can not compare type \"%s\" and \"%s\"", reflect.TypeOf(value), reflect.TypeOf(prevValue)), msgAndArgs...)
return Fail(t, fmt.Sprintf(`Can not compare type "%T" and "%T"`, value, prevValue), msgAndArgs...)
}
if !containsValue(allowedComparesResults, compareResult) {

View File

@ -24,6 +24,8 @@ import (
"github.com/stretchr/testify/assert/yaml"
)
const stackFrameBufferSize = 10
//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=assert -template=assertion_format.go.tmpl"
// TestingT is an interface wrapper around *testing.T
@ -212,57 +214,73 @@ the problem actually occurred in calling code.*/
func CallerInfo() []string {
var pc uintptr
var ok bool
var file string
var line int
var name string
callers := []string{}
for i := 0; ; i++ {
pc, file, line, ok = runtime.Caller(i)
if !ok {
// The breaks below failed to terminate the loop, and we ran off the
// end of the call stack.
pcs := make([]uintptr, stackFrameBufferSize)
offset := 1
for {
n := runtime.Callers(offset, pcs)
if n == 0 {
break
}
// This is a huge edge case, but it will panic if this is the case, see #180
if file == "<autogenerated>" {
break
}
frames := runtime.CallersFrames(pcs[:n])
f := runtime.FuncForPC(pc)
if f == nil {
break
}
name = f.Name()
for {
frame, more := frames.Next()
pc = frame.PC
file = frame.File
line = frame.Line
// testing.tRunner is the standard library function that calls
// tests. Subtests are called directly by tRunner, without going through
// the Test/Benchmark/Example function that contains the t.Run calls, so
// with subtests we should break when we hit tRunner, without adding it
// to the list of callers.
if name == "testing.tRunner" {
break
}
// This is a huge edge case, but it will panic if this is the case, see #180
if file == "<autogenerated>" {
break
}
parts := strings.Split(file, "/")
if len(parts) > 1 {
filename := parts[len(parts)-1]
dir := parts[len(parts)-2]
if (dir != "assert" && dir != "mock" && dir != "require") || filename == "mock_test.go" {
callers = append(callers, fmt.Sprintf("%s:%d", file, line))
f := runtime.FuncForPC(pc)
if f == nil {
break
}
name = f.Name()
// testing.tRunner is the standard library function that calls
// tests. Subtests are called directly by tRunner, without going through
// the Test/Benchmark/Example function that contains the t.Run calls, so
// with subtests we should break when we hit tRunner, without adding it
// to the list of callers.
if name == "testing.tRunner" {
break
}
parts := strings.Split(file, "/")
if len(parts) > 1 {
filename := parts[len(parts)-1]
dir := parts[len(parts)-2]
if (dir != "assert" && dir != "mock" && dir != "require") || filename == "mock_test.go" {
callers = append(callers, fmt.Sprintf("%s:%d", file, line))
}
}
// Drop the package
segments := strings.Split(name, ".")
name = segments[len(segments)-1]
if isTest(name, "Test") ||
isTest(name, "Benchmark") ||
isTest(name, "Example") {
break
}
if !more {
break
}
}
// We know we already have less than a buffer's worth of frames
offset += stackFrameBufferSize
// Drop the package
segments := strings.Split(name, ".")
name = segments[len(segments)-1]
if isTest(name, "Test") ||
isTest(name, "Benchmark") ||
isTest(name, "Example") {
break
}
}
return callers
@ -444,7 +462,7 @@ func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs
}
if !ObjectsAreEqual(reflect.TypeOf(object), reflect.TypeOf(expectedType)) {
return Fail(t, fmt.Sprintf("Object expected to be of type %v, but was %v", reflect.TypeOf(expectedType), reflect.TypeOf(object)), msgAndArgs...)
return Fail(t, fmt.Sprintf("Object expected to be of type %T, but was %T", expectedType, object), msgAndArgs...)
}
return true
@ -1069,7 +1087,7 @@ func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{})
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("%q could not be applied builtin len()", list), msgAndArgs...)
}
if !found {
return true
@ -2102,7 +2120,7 @@ func ErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool {
expectedText = target.Error()
}
chain := buildErrorChainString(err)
chain := buildErrorChainString(err, false)
return Fail(t, fmt.Sprintf("Target error should be in err chain:\n"+
"expected: %q\n"+
@ -2125,7 +2143,7 @@ func NotErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool {
expectedText = target.Error()
}
chain := buildErrorChainString(err)
chain := buildErrorChainString(err, false)
return Fail(t, fmt.Sprintf("Target error should not be in err chain:\n"+
"found: %q\n"+
@ -2143,11 +2161,11 @@ func ErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{
return true
}
chain := buildErrorChainString(err)
chain := buildErrorChainString(err, true)
return Fail(t, fmt.Sprintf("Should be in error chain:\n"+
"expected: %q\n"+
"in chain: %s", target, chain,
"expected: %s\n"+
"in chain: %s", reflect.ValueOf(target).Elem().Type(), chain,
), msgAndArgs...)
}
@ -2161,24 +2179,46 @@ func NotErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interfa
return true
}
chain := buildErrorChainString(err)
chain := buildErrorChainString(err, true)
return Fail(t, fmt.Sprintf("Target error should not be in err chain:\n"+
"found: %q\n"+
"in chain: %s", target, chain,
"found: %s\n"+
"in chain: %s", reflect.ValueOf(target).Elem().Type(), chain,
), msgAndArgs...)
}
func buildErrorChainString(err error) string {
func unwrapAll(err error) (errs []error) {
errs = append(errs, err)
switch x := err.(type) {
case interface{ Unwrap() error }:
err = x.Unwrap()
if err == nil {
return
}
errs = append(errs, unwrapAll(err)...)
case interface{ Unwrap() []error }:
for _, err := range x.Unwrap() {
errs = append(errs, unwrapAll(err)...)
}
}
return
}
func buildErrorChainString(err error, withType bool) string {
if err == nil {
return ""
}
e := errors.Unwrap(err)
chain := fmt.Sprintf("%q", err.Error())
for e != nil {
chain += fmt.Sprintf("\n\t%q", e.Error())
e = errors.Unwrap(e)
var chain string
errs := unwrapAll(err)
for i := range errs {
if i != 0 {
chain += "\n\t"
}
chain += fmt.Sprintf("%q", errs[i].Error())
if withType {
chain += fmt.Sprintf(" (%T)", errs[i])
}
}
return chain
}

View File

@ -2118,7 +2118,7 @@ func TestRegexp(t *testing.T) {
}
for _, tc := range cases {
False(t, Regexp(mockT, tc.rx, tc.str), "Expected \"%s\" to not match \"%s\"", tc.rx, tc.str)
False(t, Regexp(mockT, tc.rx, tc.str), "Expected %q to not match %q", tc.rx, tc.str)
False(t, Regexp(mockT, regexp.MustCompile(tc.rx), tc.str))
False(t, Regexp(mockT, regexp.MustCompile(tc.rx), []byte(tc.str)))
True(t, NotRegexp(mockT, tc.rx, tc.str))
@ -2151,11 +2151,11 @@ func TestZero(t *testing.T) {
mockT := new(testing.T)
for _, test := range zeros {
True(t, Zero(mockT, test, "%#v is not the %v zero value", test, reflect.TypeOf(test)))
True(t, Zero(mockT, test, "%#v is not the %T zero value", test, test))
}
for _, test := range nonZeros {
False(t, Zero(mockT, test, "%#v is not the %v zero value", test, reflect.TypeOf(test)))
False(t, Zero(mockT, test, "%#v is not the %T zero value", test, test))
}
}
@ -2163,11 +2163,11 @@ func TestNotZero(t *testing.T) {
mockT := new(testing.T)
for _, test := range zeros {
False(t, NotZero(mockT, test, "%#v is not the %v zero value", test, reflect.TypeOf(test)))
False(t, NotZero(mockT, test, "%#v is not the %T zero value", test, test))
}
for _, test := range nonZeros {
True(t, NotZero(mockT, test, "%#v is not the %v zero value", test, reflect.TypeOf(test)))
True(t, NotZero(mockT, test, "%#v is not the %T zero value", test, test))
}
}
@ -3175,11 +3175,13 @@ func parseLabeledOutput(output string) []labeledContent {
}
type captureTestingT struct {
msg string
failed bool
msg string
}
func (ctt *captureTestingT) Errorf(format string, args ...interface{}) {
ctt.msg = fmt.Sprintf(format, args...)
ctt.failed = true
}
func (ctt *captureTestingT) checkResultAndErrMsg(t *testing.T, expectedRes, res bool, expectedErrMsg string) {
@ -3188,6 +3190,10 @@ func (ctt *captureTestingT) checkResultAndErrMsg(t *testing.T, expectedRes, res
t.Errorf("Should return %t", expectedRes)
return
}
if res == ctt.failed {
t.Errorf("The test result (%t) should be reflected in the testing.T type (%t)", res, !ctt.failed)
return
}
contents := parseLabeledOutput(ctt.msg)
if res == true {
if contents != nil {
@ -3348,50 +3354,82 @@ func TestNotErrorIs(t *testing.T) {
func TestErrorAs(t *testing.T) {
tests := []struct {
err error
result bool
err error
result bool
resultErrMsg string
}{
{fmt.Errorf("wrap: %w", &customError{}), true},
{io.EOF, false},
{nil, false},
{
err: fmt.Errorf("wrap: %w", &customError{}),
result: true,
},
{
err: io.EOF,
result: false,
resultErrMsg: "" +
"Should be in error chain:\n" +
"expected: *assert.customError\n" +
"in chain: \"EOF\" (*errors.errorString)\n",
},
{
err: nil,
result: false,
resultErrMsg: "" +
"Should be in error chain:\n" +
"expected: *assert.customError\n" +
"in chain: \n",
},
{
err: fmt.Errorf("abc: %w", errors.New("def")),
result: false,
resultErrMsg: "" +
"Should be in error chain:\n" +
"expected: *assert.customError\n" +
"in chain: \"abc: def\" (*fmt.wrapError)\n" +
"\t\"def\" (*errors.errorString)\n",
},
}
for _, tt := range tests {
tt := tt
var target *customError
t.Run(fmt.Sprintf("ErrorAs(%#v,%#v)", tt.err, target), func(t *testing.T) {
mockT := new(testing.T)
mockT := new(captureTestingT)
res := ErrorAs(mockT, tt.err, &target)
if res != tt.result {
t.Errorf("ErrorAs(%#v,%#v) should return %t", tt.err, target, tt.result)
}
if res == mockT.Failed() {
t.Errorf("The test result (%t) should be reflected in the testing.T type (%t)", res, !mockT.Failed())
}
mockT.checkResultAndErrMsg(t, tt.result, res, tt.resultErrMsg)
})
}
}
func TestNotErrorAs(t *testing.T) {
tests := []struct {
err error
result bool
err error
result bool
resultErrMsg string
}{
{fmt.Errorf("wrap: %w", &customError{}), false},
{io.EOF, true},
{nil, true},
{
err: fmt.Errorf("wrap: %w", &customError{}),
result: false,
resultErrMsg: "" +
"Target error should not be in err chain:\n" +
"found: *assert.customError\n" +
"in chain: \"wrap: fail\" (*fmt.wrapError)\n" +
"\t\"fail\" (*assert.customError)\n",
},
{
err: io.EOF,
result: true,
},
{
err: nil,
result: true,
},
}
for _, tt := range tests {
tt := tt
var target *customError
t.Run(fmt.Sprintf("NotErrorAs(%#v,%#v)", tt.err, target), func(t *testing.T) {
mockT := new(testing.T)
mockT := new(captureTestingT)
res := NotErrorAs(mockT, tt.err, &target)
if res != tt.result {
t.Errorf("NotErrorAs(%#v,%#v) should not return %t", tt.err, target, tt.result)
}
if res == mockT.Failed() {
t.Errorf("The test result (%t) should be reflected in the testing.T type (%t)", res, !mockT.Failed())
}
mockT.checkResultAndErrMsg(t, tt.result, res, tt.resultErrMsg)
})
}
}

View File

@ -546,7 +546,7 @@ func TestRegexpWrapper(t *testing.T) {
}
for _, tc := range cases {
False(t, assert.Regexp(tc.rx, tc.str), "Expected \"%s\" to not match \"%s\"", tc.rx, tc.str)
False(t, assert.Regexp(tc.rx, tc.str), "Expected %q to not match %q", tc.rx, tc.str)
False(t, assert.Regexp(regexp.MustCompile(tc.rx), tc.str))
True(t, assert.NotRegexp(tc.rx, tc.str))
True(t, assert.NotRegexp(regexp.MustCompile(tc.rx), tc.str))

View File

@ -138,7 +138,7 @@ func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string,
contains := strings.Contains(body, fmt.Sprint(str))
if !contains {
Fail(t, fmt.Sprintf("Expected response body for \"%s\" to contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body), msgAndArgs...)
Fail(t, fmt.Sprintf("Expected response body for %q to contain %q but found %q", url+"?"+values.Encode(), str, body), msgAndArgs...)
}
return contains
@ -158,7 +158,7 @@ func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url strin
contains := strings.Contains(body, fmt.Sprint(str))
if contains {
Fail(t, fmt.Sprintf("Expected response body for \"%s\" to NOT contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body), msgAndArgs...)
Fail(t, fmt.Sprintf("Expected response body for %q to NOT contain %q but found %q", url+"?"+values.Encode(), str, body), msgAndArgs...)
}
return !contains

View File

@ -208,9 +208,16 @@ func (c *Call) On(methodName string, arguments ...interface{}) *Call {
return c.Parent.On(methodName, arguments...)
}
// Unset removes a mock handler from being called.
// Unset removes all mock handlers that satisfy the call instance arguments from being
// called. Only supported on call instances with static input arguments.
//
// test.On("func", mock.Anything).Unset()
// For example, the only handler remaining after the following would be "MyMethod(2, 2)":
//
// Mock.
// On("MyMethod", 2, 2).Return(0).
// On("MyMethod", 3, 3).Return(0).
// On("MyMethod", Anything, Anything).Return(0)
// Mock.On("MyMethod", 3, 3).Unset()
func (c *Call) Unset() *Call {
var unlockOnce sync.Once
@ -331,7 +338,10 @@ func (m *Mock) TestData() objx.Map {
Setting expectations
*/
// Test sets the test struct variable of the mock object
// Test sets the [TestingT] on which errors will be reported, otherwise errors
// will cause a panic.
// Test should not be called on an object that is going to be used in a
// goroutine other than the one running the test function.
func (m *Mock) Test(t TestingT) {
m.mutex.Lock()
defer m.mutex.Unlock()
@ -494,7 +504,7 @@ func (m *Mock) MethodCalled(methodName string, arguments ...interface{}) Argumen
// 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())
m.fail("\nassert: mock: The method has been called over %d times.\n\tEither do one more Mock.On(%#v).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())
}
// we have to fail here - because we don't know what to do
// as the return arguments. This is because:
@ -514,7 +524,7 @@ func (m *Mock) MethodCalled(methodName string, arguments ...interface{}) Argumen
assert.CallerInfo(),
)
} else {
m.fail("\nassert: mock: I don't know what to return because the method call was unexpected.\n\tEither do Mock.On(\"%s\").Return(...) first, or remove the %s() call.\n\tThis method was unexpected:\n\t\t%s\n\tat: %s", methodName, methodName, callString(methodName, arguments, true), assert.CallerInfo())
m.fail("\nassert: mock: I don't know what to return because the method call was unexpected.\n\tEither do Mock.On(%#v).Return(...) first, or remove the %s() call.\n\tThis method was unexpected:\n\t\t%s\n\tat: %s", methodName, methodName, callString(methodName, arguments, true), assert.CallerInfo())
}
}

View File

@ -23,6 +23,8 @@
//
// The `require` package have same global functions as in the `assert` package,
// but instead of returning a boolean result they call `t.FailNow()`.
// A consequence of this is that it must be called from the goroutine running
// the test function, not from other goroutines created during the test.
//
// Every assertion function also takes an optional string message as the final argument,
// allowing custom error messages to be appended to the message the assertion method outputs.

View File

@ -4,7 +4,7 @@ import (
"bytes"
"errors"
"flag"
"io/ioutil"
"io"
"math/rand"
"os"
"os/exec"
@ -440,7 +440,7 @@ func (sc *StdoutCapture) StopCapture() (string, error) {
}
os.Stdout.Close()
os.Stdout = sc.oldStdout
bytes, err := ioutil.ReadAll(sc.readPipe)
bytes, err := io.ReadAll(sc.readPipe)
if err != nil {
return "", err
}