From 3380867632684d2023eeeb0d5d1d45a7b89e6972 Mon Sep 17 00:00:00 2001 From: Pal Sivertsen Date: Tue, 30 Nov 2021 14:26:04 +0100 Subject: [PATCH 1/4] Add NotErrorAs assertion The library already had assertions for `ErrorIs`, `NotErrorIs` and `ErrorAs`. This commit adds the `NotErrorAs` assertion which is the inverse of `ErrorAs`. --- assert/assertion_format.go | 9 +++++++++ assert/assertion_forward.go | 18 ++++++++++++++++++ assert/assertions.go | 18 ++++++++++++++++++ assert/assertions_test.go | 27 ++++++++++++++++++++++++++- require/require.go | 24 ++++++++++++++++++++++++ require/require_forward.go | 18 ++++++++++++++++++ 6 files changed, 113 insertions(+), 1 deletion(-) diff --git a/assert/assertion_format.go b/assert/assertion_format.go index 546fe1fb2..1db39550d 100644 --- a/assert/assertion_format.go +++ b/assert/assertion_format.go @@ -621,6 +621,15 @@ func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg s return NotEqualValues(t, expected, actual, append([]interface{}{msg}, args...)...) } +// NotErrorAsf asserts that at none of the errors in err's chain matches target. +// This is the inverse of the ErrorAs function. +func NotErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return NotErrorAs(t, err, target, append([]interface{}{msg}, args...)...) +} + // NotErrorIsf asserts that none of the errors in err's chain matches target. // This is a wrapper for errors.Is. func NotErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool { diff --git a/assert/assertion_forward.go b/assert/assertion_forward.go index 8504dca9f..a658b4db8 100644 --- a/assert/assertion_forward.go +++ b/assert/assertion_forward.go @@ -1234,6 +1234,24 @@ func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg str return NotEqualf(a.t, expected, actual, msg, args...) } +// NotErrorAs asserts that at none of the errors in err's chain matches target. +// This is the inverse of the ErrorAs function. +func (a *Assertions) NotErrorAs(err error, target interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotErrorAs(a.t, err, target, msgAndArgs...) +} + +// NotErrorAsf asserts that at none of the errors in err's chain matches target. +// This is the inverse of the ErrorAs function. +func (a *Assertions) NotErrorAsf(err error, target interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotErrorAsf(a.t, err, target, msg, args...) +} + // NotErrorIs asserts that none of the errors in err's chain matches target. // This is a wrapper for errors.Is. func (a *Assertions) NotErrorIs(err error, target error, msgAndArgs ...interface{}) bool { diff --git a/assert/assertions.go b/assert/assertions.go index 104a0c936..5ace3d495 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -2149,6 +2149,24 @@ func ErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{ ), msgAndArgs...) } +// NotErrorAs asserts that at none of the errors in err's chain matches target. +// This is the inverse of the ErrorAs function. +func NotErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !errors.As(err, target) { + return true + } + + chain := buildErrorChainString(err) + + return Fail(t, fmt.Sprintf("Target error should not be in err chain:\n"+ + "found: %q\n"+ + "in chain: %s", target, chain, + ), msgAndArgs...) +} + func buildErrorChainString(err error) string { if err == nil { return "" diff --git a/assert/assertions_test.go b/assert/assertions_test.go index 064b92f4a..228f20ac4 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -3284,7 +3284,32 @@ func TestErrorAs(t *testing.T) { t.Run(fmt.Sprintf("ErrorAs(%#v,%#v)", tt.err, target), func(t *testing.T) { res := ErrorAs(mockT, tt.err, &target) if res != tt.result { - t.Errorf("ErrorAs(%#v,%#v) should return %t)", tt.err, target, tt.result) + t.Errorf("ErrorAs(%#v,%#v) should return %t", tt.err, target, tt.result) + } + }) + } +} + +func TestNotErrorAs(t *testing.T) { + tests := []struct { + err error + result bool + }{ + {fmt.Errorf("wrap: %w", &customError{}), false}, + {io.EOF, true}, + {nil, 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) + 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()) } }) } diff --git a/require/require.go b/require/require.go index d0c73ff13..bf9877cfa 100644 --- a/require/require.go +++ b/require/require.go @@ -1559,6 +1559,30 @@ func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, t.FailNow() } +// NotErrorAs asserts that at none of the errors in err's chain matches target. +// This is the inverse of the ErrorAs function. +func NotErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotErrorAs(t, err, target, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotErrorAsf asserts that at none of the errors in err's chain matches target. +// This is the inverse of the ErrorAs function. +func NotErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotErrorAsf(t, err, target, msg, args...) { + return + } + t.FailNow() +} + // NotErrorIs asserts that none of the errors in err's chain matches target. // This is a wrapper for errors.Is. func NotErrorIs(t TestingT, err error, target error, msgAndArgs ...interface{}) { diff --git a/require/require_forward.go b/require/require_forward.go index 3c15ca36b..521daaf1a 100644 --- a/require/require_forward.go +++ b/require/require_forward.go @@ -1235,6 +1235,24 @@ func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg str NotEqualf(a.t, expected, actual, msg, args...) } +// NotErrorAs asserts that at none of the errors in err's chain matches target. +// This is the inverse of the ErrorAs function. +func (a *Assertions) NotErrorAs(err error, target interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotErrorAs(a.t, err, target, msgAndArgs...) +} + +// NotErrorAsf asserts that at none of the errors in err's chain matches target. +// This is the inverse of the ErrorAs function. +func (a *Assertions) NotErrorAsf(err error, target interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotErrorAsf(a.t, err, target, msg, args...) +} + // NotErrorIs asserts that none of the errors in err's chain matches target. // This is a wrapper for errors.Is. func (a *Assertions) NotErrorIs(err error, target error, msgAndArgs ...interface{}) { From aade8450b3812d4f2e5d4cb65769b264c4604328 Mon Sep 17 00:00:00 2001 From: Pal Sivertsen Date: Tue, 30 Nov 2021 14:29:39 +0100 Subject: [PATCH 2/4] Improve tests for ErrorIs/ErrorAs Checks that the assertion result matches what's set in `testing.T`. --- assert/assertions_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/assert/assertions_test.go b/assert/assertions_test.go index 228f20ac4..20d63b108 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -3269,7 +3269,6 @@ func TestNotErrorIs(t *testing.T) { } func TestErrorAs(t *testing.T) { - mockT := new(testing.T) tests := []struct { err error result bool @@ -3282,10 +3281,14 @@ func TestErrorAs(t *testing.T) { tt := tt var target *customError t.Run(fmt.Sprintf("ErrorAs(%#v,%#v)", tt.err, target), func(t *testing.T) { + mockT := new(testing.T) 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()) + } }) } } From dc100b1be3ac644d59bccf1f4b9b00261eb10f6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20Sivertsen?= Date: Fri, 4 Oct 2024 09:52:45 +0200 Subject: [PATCH 3/4] Review: Drop doc line and fix typo Review feedback: https://github.com/stretchr/testify/pull/1129#discussion_r1786495803 --- assert/assertion_format.go | 3 +-- assert/assertion_forward.go | 6 ++---- assert/assertions.go | 3 +-- require/require.go | 6 ++---- require/require_forward.go | 6 ++---- 5 files changed, 8 insertions(+), 16 deletions(-) diff --git a/assert/assertion_format.go b/assert/assertion_format.go index 1db39550d..2dff9e457 100644 --- a/assert/assertion_format.go +++ b/assert/assertion_format.go @@ -621,8 +621,7 @@ func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg s return NotEqualValues(t, expected, actual, append([]interface{}{msg}, args...)...) } -// NotErrorAsf asserts that at none of the errors in err's chain matches target. -// This is the inverse of the ErrorAs function. +// NotErrorAsf asserts that none of the errors in err's chain matches target. func NotErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() diff --git a/assert/assertion_forward.go b/assert/assertion_forward.go index a658b4db8..7bcc9f07a 100644 --- a/assert/assertion_forward.go +++ b/assert/assertion_forward.go @@ -1234,8 +1234,7 @@ func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg str return NotEqualf(a.t, expected, actual, msg, args...) } -// NotErrorAs asserts that at none of the errors in err's chain matches target. -// This is the inverse of the ErrorAs function. +// NotErrorAs asserts that none of the errors in err's chain matches target. func (a *Assertions) NotErrorAs(err error, target interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1243,8 +1242,7 @@ func (a *Assertions) NotErrorAs(err error, target interface{}, msgAndArgs ...int return NotErrorAs(a.t, err, target, msgAndArgs...) } -// NotErrorAsf asserts that at none of the errors in err's chain matches target. -// This is the inverse of the ErrorAs function. +// NotErrorAsf asserts that none of the errors in err's chain matches target. func (a *Assertions) NotErrorAsf(err error, target interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() diff --git a/assert/assertions.go b/assert/assertions.go index 5ace3d495..4ebe30279 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -2149,8 +2149,7 @@ func ErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{ ), msgAndArgs...) } -// NotErrorAs asserts that at none of the errors in err's chain matches target. -// This is the inverse of the ErrorAs function. +// NotErrorAs asserts that none of the errors in err's chain matches target. func NotErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() diff --git a/require/require.go b/require/require.go index bf9877cfa..9871d0855 100644 --- a/require/require.go +++ b/require/require.go @@ -1559,8 +1559,7 @@ func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, t.FailNow() } -// NotErrorAs asserts that at none of the errors in err's chain matches target. -// This is the inverse of the ErrorAs function. +// NotErrorAs asserts that none of the errors in err's chain matches target. func NotErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1571,8 +1570,7 @@ func NotErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interfa t.FailNow() } -// NotErrorAsf asserts that at none of the errors in err's chain matches target. -// This is the inverse of the ErrorAs function. +// NotErrorAsf asserts that none of the errors in err's chain matches target. func NotErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() diff --git a/require/require_forward.go b/require/require_forward.go index 521daaf1a..34ac53318 100644 --- a/require/require_forward.go +++ b/require/require_forward.go @@ -1235,8 +1235,7 @@ func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg str NotEqualf(a.t, expected, actual, msg, args...) } -// NotErrorAs asserts that at none of the errors in err's chain matches target. -// This is the inverse of the ErrorAs function. +// NotErrorAs asserts that none of the errors in err's chain matches target. func (a *Assertions) NotErrorAs(err error, target interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1244,8 +1243,7 @@ func (a *Assertions) NotErrorAs(err error, target interface{}, msgAndArgs ...int NotErrorAs(a.t, err, target, msgAndArgs...) } -// NotErrorAsf asserts that at none of the errors in err's chain matches target. -// This is the inverse of the ErrorAs function. +// NotErrorAsf asserts that none of the errors in err's chain matches target. func (a *Assertions) NotErrorAsf(err error, target interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() From f844b269dfdaed00db57ff08e2ff601daa349e00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20Sivertsen?= Date: Fri, 4 Oct 2024 12:37:24 +0200 Subject: [PATCH 4/4] Review: Expand NotErrorAs func docs https://github.com/stretchr/testify/pull/1129#discussion_r1787490770 --- assert/assertion_format.go | 3 ++- assert/assertion_forward.go | 6 ++++-- assert/assertions.go | 3 ++- require/require.go | 6 ++++-- require/require_forward.go | 6 ++++-- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/assert/assertion_format.go b/assert/assertion_format.go index 2dff9e457..190634165 100644 --- a/assert/assertion_format.go +++ b/assert/assertion_format.go @@ -621,7 +621,8 @@ func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg s return NotEqualValues(t, expected, actual, append([]interface{}{msg}, args...)...) } -// NotErrorAsf asserts that none of the errors in err's chain matches target. +// NotErrorAsf asserts that none of the errors in err's chain matches target, +// but if so, sets target to that error value. func NotErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() diff --git a/assert/assertion_forward.go b/assert/assertion_forward.go index 7bcc9f07a..21629087b 100644 --- a/assert/assertion_forward.go +++ b/assert/assertion_forward.go @@ -1234,7 +1234,8 @@ func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg str return NotEqualf(a.t, expected, actual, msg, args...) } -// NotErrorAs asserts that none of the errors in err's chain matches target. +// NotErrorAs asserts that none of the errors in err's chain matches target, +// but if so, sets target to that error value. func (a *Assertions) NotErrorAs(err error, target interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1242,7 +1243,8 @@ func (a *Assertions) NotErrorAs(err error, target interface{}, msgAndArgs ...int return NotErrorAs(a.t, err, target, msgAndArgs...) } -// NotErrorAsf asserts that none of the errors in err's chain matches target. +// NotErrorAsf asserts that none of the errors in err's chain matches target, +// but if so, sets target to that error value. func (a *Assertions) NotErrorAsf(err error, target interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() diff --git a/assert/assertions.go b/assert/assertions.go index 4ebe30279..44b854da6 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -2149,7 +2149,8 @@ func ErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{ ), msgAndArgs...) } -// NotErrorAs asserts that none of the errors in err's chain matches target. +// NotErrorAs asserts that none of the errors in err's chain matches target, +// but if so, sets target to that error value. func NotErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() diff --git a/require/require.go b/require/require.go index 9871d0855..50ec19e13 100644 --- a/require/require.go +++ b/require/require.go @@ -1559,7 +1559,8 @@ func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, t.FailNow() } -// NotErrorAs asserts that none of the errors in err's chain matches target. +// NotErrorAs asserts that none of the errors in err's chain matches target, +// but if so, sets target to that error value. func NotErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1570,7 +1571,8 @@ func NotErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interfa t.FailNow() } -// NotErrorAsf asserts that none of the errors in err's chain matches target. +// NotErrorAsf asserts that none of the errors in err's chain matches target, +// but if so, sets target to that error value. func NotErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() diff --git a/require/require_forward.go b/require/require_forward.go index 34ac53318..1bd87304f 100644 --- a/require/require_forward.go +++ b/require/require_forward.go @@ -1235,7 +1235,8 @@ func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg str NotEqualf(a.t, expected, actual, msg, args...) } -// NotErrorAs asserts that none of the errors in err's chain matches target. +// NotErrorAs asserts that none of the errors in err's chain matches target, +// but if so, sets target to that error value. func (a *Assertions) NotErrorAs(err error, target interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1243,7 +1244,8 @@ func (a *Assertions) NotErrorAs(err error, target interface{}, msgAndArgs ...int NotErrorAs(a.t, err, target, msgAndArgs...) } -// NotErrorAsf asserts that none of the errors in err's chain matches target. +// NotErrorAsf asserts that none of the errors in err's chain matches target, +// but if so, sets target to that error value. func (a *Assertions) NotErrorAsf(err error, target interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper()