Chapter 21: Panic Recovery¶
Description¶
Test panic recovery with recover() inside defer to convert panics into errors. Production code uses defer/recover to prevent crashes from division by zero, nil pointer dereference, or invalid input. Tests exercise both the panic path (via assert.Panics) and the recovered-error path (via SafeDivide, SafeExecute).
Code¶
var ErrPanic = errors.New("panic recovered")
func SafeDivide(a, b int) (result int, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("%w: %v", ErrPanic, r)
}
}()
return a / b, nil
}
func SafeExecute(fn func()) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("%w: %v", ErrPanic, r)
}
}()
fn()
return nil
}
Test¶
func TestSafeDivide(t *testing.T) {
t.Run("normal division", func(t *testing.T) {
r, err := SafeDivide(10, 3)
require.NoError(t, err)
assert.Equal(t, 3, r)
})
t.Run("division by zero", func(t *testing.T) {
_, err := SafeDivide(1, 0)
assert.Error(t, err)
assert.ErrorIs(t, err, ErrPanic)
assert.Contains(t, err.Error(), "integer divide by zero")
})
}
func TestMustParse(t *testing.T) {
t.Run("valid number", func(t *testing.T) {
n, err := MustParse("42")
require.NoError(t, err)
assert.Equal(t, 42, n)
})
t.Run("empty string", func(t *testing.T) {
_, err := MustParse("")
assert.Error(t, err)
assert.ErrorIs(t, err, ErrPanic)
})
t.Run("invalid character", func(t *testing.T) {
_, err := MustParse("12a34")
assert.Error(t, err)
assert.ErrorIs(t, err, ErrPanic)
})
}
func TestPanicIfNegative(t *testing.T) {
assert.Equal(t, 10, PanicIfNegative(5))
assert.Panics(t, func() { PanicIfNegative(-1) })
assert.PanicsWithValue(t, "negative value not allowed", func() {
PanicIfNegative(-5)
})
}
func TestSafeExecute(t *testing.T) {
t.Run("no panic", func(t *testing.T) {
called := false
err := SafeExecute(func() { called = true })
require.NoError(t, err)
assert.True(t, called)
})
t.Run("panic recovered", func(t *testing.T) {
err := SafeExecute(func() { panic("boom") })
assert.Error(t, err)
assert.ErrorIs(t, err, ErrPanic)
})
}
Testing Approach¶
Panic recovery testing:
- Named returns for defer —
(result int, err error)gives the deferred closure access to the return values. The deferredrecoversetserrbefore the function returns. assert.Panicsfor expected panics —PanicIfNegative(-1)should panic.assert.Panicscatches it and verifies the panic happened.PanicsWithValuechecks the exact panic value.assert.ErrorIsfor sentinel errors —ErrPanicwraps the original panic value. Tests assertErrorIs(ErrPanic)to confirm recovery happened, plusContainsfor the underlying reason.- Safe wrapper pattern —
SafeExecute(fn)demonstrates recovering a panic from any function. Useful for wrapping third-party code or callbacks that might panic.
View source code on GitHub