Repository: stretchr/testify (go)

Issue:
PanicsWithValue() should use equality, not ==
Panic with Error instead of string is a valid practice. Asserting the value of a panic using `!=` is not ok, because it results in a funny, but annoying test fails.
 
Example of a fail
```
=== RUN   Test
--- FAIL: Test (0.00s)
    test.go:27: 
        	Error Trace:	test.go:27
        	Error:      	func (assert.PanicTestFunc)(0x128adb0) should panic with value:	&errors.errorString{s:"message"}
        	            		Panic value:	&errors.errorString{s:"message"}
        	Test:       	Test
FAIL
```

The causing code (assertions.go:952):
```
if panicValue != expected {
    return Fail(t, fmt.Sprintf("func %#v should panic with value:\t%#v\n\tPanic value:\t%#v", f, expected, panicValue), msgAndArgs...)
}
```

Pull request description:
Add PanicsWithError
# What
Add `PanicsWithError` that behaves like `PanicsWithValue` but requires
the value to be an `error` and compares an error string against the
error in the same fashion as `EqualError`.

# Why
`PanicsWithValue` is very useful with the value that will panic is a
basic type that can be easily defined in a test, but is less useful for
validating an error because error values may not be easily recreated in
a test to exactly match the error that will be generated in the moment.
This can be because an error will contain fields that aren't exported
and the test may not be able to define the exact error. Most of the time
testing error objects just testing the string is sufficient. This
concept is already supported by this module in the existing `EqualError`
function.

Fixes #749

Patch:
diff --git a/assert/assertion_format.go b/assert/assertion_format.go
index f17d838..7804ff0 100644
--- a/assert/assertion_format.go
+++ b/assert/assertion_format.go
@@ -496,6 +496,18 @@ func Panicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool
 	return Panics(t, f, append([]interface{}{msg}, args...)...)
 }
 
+// PanicsWithErrorf asserts that the code inside the specified PanicTestFunc
+// panics, and that the recovered panic value is an error that satisfies the
+// EqualError comparison.
+//
+//   assert.PanicsWithErrorf(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
+func PanicsWithErrorf(t TestingT, errString string, f PanicTestFunc, msg string, args ...interface{}) bool {
+	if h, ok := t.(tHelper); ok {
+		h.Helper()
+	}
+	return PanicsWithError(t, errString, f, append([]interface{}{msg}, args...)...)
+}
+
 // PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that
 // the recovered panic value equals the expected panic value.
 //
diff --git a/assert/assertion_forward.go b/assert/assertion_forward.go
index 1fa2e32..cd9c7ef 100644
--- a/assert/assertion_forward.go
+++ b/assert/assertion_forward.go
@@ -971,6 +971,30 @@ func (a *Assertions) Panics(f PanicTestFunc, msgAndArgs ...interface{}) bool {
 	return Panics(a.t, f, msgAndArgs...)
 }
 
+// PanicsWithError asserts that the code inside the specified PanicTestFunc
+// panics, and that the recovered panic value is an error that satisfies the
+// EqualError comparison.
+//
+//   a.PanicsWithError("crazy error", func(){ GoCrazy() })
+func (a *Assertions) PanicsWithError(errString string, f PanicTestFunc, msgAndArgs ...interface{}) bool {
+	if h, ok := a.t.(tHelper); ok {
+		h.Helper()
+	}
+	return PanicsWithError(a.t, errString, f, msgAndArgs...)
+}
+
+// PanicsWithErrorf asserts that the code inside the specified PanicTestFunc
+// panics, and that the recovered panic value is an error that satisfies the
+// EqualError comparison.
+//
+//   a.PanicsWithErrorf("crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
+func (a *Assertions) PanicsWithErrorf(errString string, f PanicTestFunc, msg string, args ...interface{}) bool {
+	if h, ok := a.t.(tHelper); ok {
+		h.Helper()
+	}
+	return PanicsWithErrorf(a.t, errString, f, msg, args...)
+}
+
 // PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that
 // the recovered panic value equals the expected panic value.
 //
diff --git a/assert/assertions.go b/assert/assertions.go
index 3d5ebea..2ff827a 100644
--- a/assert/assertions.go
+++ b/assert/assertions.go
@@ -985,6 +985,28 @@ func PanicsWithValue(t TestingT, expected interface{}, f PanicTestFunc, msgAndAr
 	return true
 }
 
+// PanicsWithError asserts that the code inside the specified PanicTestFunc
+// panics, and that the recovered panic value is an error that satisfies the
+// EqualError comparison.
+//
+//   assert.PanicsWithError(t, "crazy error", func(){ GoCrazy() })
+func PanicsWithError(t TestingT, errString string, f PanicTestFunc, msgAndArgs ...interface{}) bool {
+	if h, ok := t.(tHelper); ok {
+		h.Helper()
+	}
+
+	funcDidPanic, panicValue, panickedStack := didPanic(f)
+	if !funcDidPanic {
+		return Fail(t, fmt.Sprintf("func %#v should panic\n\tPanic value:\t%#v", f, panicValue), msgAndArgs...)
+	}
+	panicErr, ok := panicValue.(error)
+	if !ok || panicErr.Error() != errString {
+		return Fail(t, fmt.Sprintf("func %#v should panic with error message:\t%#v\n\tPanic value:\t%#v\n\tPanic stack:\t%s", f, errString, panicValue, panickedStack), msgAndArgs...)
+	}
+
+	return true
+}
+
 // NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic.
 //
 //   assert.NotPanics(t, func(){ RemainCalm() })
diff --git a/require/require.go b/require/require.go
index 068e521..1bfb086 100644
--- a/require/require.go
+++ b/require/require.go
@@ -1239,6 +1239,36 @@ func Panics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) {
 	t.FailNow()
 }
 
+// PanicsWithError asserts that the code inside the specified PanicTestFunc
+// panics, and that the recovered panic value is an error that satisfies the
+// EqualError comparison.
+//
+//   assert.PanicsWithError(t, "crazy error", func(){ GoCrazy() })
+func PanicsWithError(t TestingT, errString string, f assert.PanicTestFunc, msgAndArgs ...interface{}) {
+	if h, ok := t.(tHelper); ok {
+		h.Helper()
+	}
+	if assert.PanicsWithError(t, errString, f, msgAndArgs...) {
+		return
+	}
+	t.FailNow()
+}
+
+// PanicsWithErrorf asserts that the code inside the specified PanicTestFunc
+// panics, and that the recovered panic value is an error that satisfies the
+// EqualError comparison.
+//
+//   assert.PanicsWithErrorf(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
+func PanicsWithErrorf(t TestingT, errString string, f assert.PanicTestFunc, msg string, args ...interface{}) {
+	if h, ok := t.(tHelper); ok {
+		h.Helper()
+	}
+	if assert.PanicsWithErrorf(t, errString, f, msg, args...) {
+		return
+	}
+	t.FailNow()
+}
+
 // PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that
 // the recovered panic value equals the expected panic value.
 //
diff --git a/require/require_forward.go b/require/require_forward.go
index 1ef2e63..11326bf 100644
--- a/require/require_forward.go
+++ b/require/require_forward.go
@@ -972,6 +972,30 @@ func (a *Assertions) Panics(f assert.PanicTestFunc, msgAndArgs ...interface{}) {
 	Panics(a.t, f, msgAndArgs...)
 }
 
+// PanicsWithError asserts that the code inside the specified PanicTestFunc
+// panics, and that the recovered panic value is an error that satisfies the
+// EqualError comparison.
+//
+//   a.PanicsWithError("crazy error", func(){ GoCrazy() })
+func (a *Assertions) PanicsWithError(errString string, f assert.PanicTestFunc, msgAndArgs ...interface{}) {
+	if h, ok := a.t.(tHelper); ok {
+		h.Helper()
+	}
+	PanicsWithError(a.t, errString, f, msgAndArgs...)
+}
+
+// PanicsWithErrorf asserts that the code inside the specified PanicTestFunc
+// panics, and that the recovered panic value is an error that satisfies the
+// EqualError comparison.
+//
+//   a.PanicsWithErrorf("crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
+func (a *Assertions) PanicsWithErrorf(errString string, f assert.PanicTestFunc, msg string, args ...interface{}) {
+	if h, ok := a.t.(tHelper); ok {
+		h.Helper()
+	}
+	PanicsWithErrorf(a.t, errString, f, msg, args...)
+}
+
 // PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that
 // the recovered panic value equals the expected panic value.
 //


Test patch:
diff --git a/assert/assertions_test.go b/assert/assertions_test.go
index 93b78c4..2aafb25 100644
--- a/assert/assertions_test.go
+++ b/assert/assertions_test.go
@@ -846,6 +846,34 @@ func TestPanicsWithValue(t *testing.T) {
 	}
 }
 
+func TestPanicsWithError(t *testing.T) {
+
+	mockT := new(testing.T)
+
+	if !PanicsWithError(mockT, "Panic!", func() {
+		panic(errors.New("Panic!"))
+	}) {
+		t.Error("PanicsWithError should return true")
+	}
+
+	if PanicsWithError(mockT, "Panic!", func() {
+	}) {
+		t.Error("PanicsWithError should return false")
+	}
+
+	if PanicsWithError(mockT, "at the disco", func() {
+		panic(errors.New("Panic!"))
+	}) {
+		t.Error("PanicsWithError should return false")
+	}
+
+	if PanicsWithError(mockT, "Panic!", func() {
+		panic("Panic!")
+	}) {
+		t.Error("PanicsWithError should return false")
+	}
+}
+
 func TestNotPanics(t *testing.T) {
 
 	mockT := new(testing.T)