Skip to content

Commit aaeaa5d

Browse files
authored
compare unwrapped errors using DeepEqual (#617)
1 parent 9351dda commit aaeaa5d

File tree

3 files changed

+29
-2
lines changed

3 files changed

+29
-2
lines changed

docs/index.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -794,7 +794,9 @@ succeeds if `ACTUAL` is a non-nil `error` that matches `EXPECTED`. `EXPECTED` mu
794794

795795
- A string, in which case `ACTUAL.Error()` will be compared against `EXPECTED`.
796796
- A matcher, in which case `ACTUAL.Error()` is tested against the matcher.
797-
- An error, in which case `ACTUAL` and `EXPECTED` are compared via `reflect.DeepEqual()`. If they are not deeply equal, they are tested by `errors.Is(ACTUAL, EXPECTED)`. (The latter allows to test whether `ACTUAL` wraps an `EXPECTED` error.)
797+
- An error, in which case anyo of the following is satisfied:
798+
- `errors.Is(ACTUAL, EXPECTED)` returns `true`
799+
- `ACTUAL` or any of the errors it wraps (directly or indirectly) equals `EXPECTED` in terms of `reflect.DeepEqual()`.
798800

799801
Any other type for `EXPECTED` is an error.
800802

matchers/match_error_matcher.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,17 @@ func (matcher *MatchErrorMatcher) Match(actual interface{}) (success bool, err e
2525
expected := matcher.Expected
2626

2727
if isError(expected) {
28-
return reflect.DeepEqual(actualErr, expected) || errors.Is(actualErr, expected.(error)), nil
28+
// first try the built-in errors.Is
29+
if errors.Is(actualErr, expected.(error)) {
30+
return true, nil
31+
}
32+
// if not, try DeepEqual along the error chain
33+
for unwrapped := actualErr; unwrapped != nil; unwrapped = errors.Unwrap(unwrapped) {
34+
if reflect.DeepEqual(unwrapped, expected) {
35+
return true, nil
36+
}
37+
}
38+
return false, nil
2939
}
3040

3141
if isString(expected) {

matchers/match_error_matcher_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ func (c CustomError) Error() string {
1616
return "an error"
1717
}
1818

19+
type ComplexError struct {
20+
Key string
21+
}
22+
23+
func (t *ComplexError) Error() string {
24+
return fmt.Sprintf("err: %s", t.Key)
25+
}
26+
1927
var _ = Describe("MatchErrorMatcher", func() {
2028
Context("When asserting against an error", func() {
2129
When("passed an error", func() {
@@ -37,6 +45,12 @@ var _ = Describe("MatchErrorMatcher", func() {
3745

3846
Expect(outerErr).Should(MatchError(innerErr))
3947
})
48+
49+
It("uses deep equality with unwrapped errors", func() {
50+
innerErr := &ComplexError{Key: "abc"}
51+
outerErr := fmt.Errorf("outer error wrapping: %w", &ComplexError{Key: "abc"})
52+
Expect(outerErr).To(MatchError(innerErr))
53+
})
4054
})
4155

4256
When("actual an expected are both pointers to an error", func() {
@@ -130,6 +144,7 @@ var _ = Describe("MatchErrorMatcher", func() {
130144
})
131145
Expect(failuresMessages[0]).To(ContainSubstring("{s: \"foo\"}\nnot to match error\n <string>: foo"))
132146
})
147+
133148
})
134149

135150
type mockErr string

0 commit comments

Comments
 (0)