You want to call a panic in a piece of code and have the test not fail?

In a deferred function, call recover(). If it has a return value that is not nil, it succeeded and caught the panic i.e. if err == nil then you can call t.Fatal() in your test.

Here's the setup code

Let's start a new package to demonstrate this:

mkdir panicker && cd panicker && go mod init panicker && touch panicker_test.go

Here's the content of panicker_test.go:

package panicker
import "testing"

func Panics() {
    panic("I panic!")
}

func TestPanics(t *testing.T) {
    Panics()
}       

Here's how she runs:

; go test -v panicker
=== RUN   TestPanics
--- FAIL: TestPanics (0.00s)
panic: I panic! [recovered]
        panic: I panic!

goroutine 34 [running]:
testing.tRunner.func1.2({0x10441d540, 0x1044455e0})
/opt/homebrew/Cellar/go/1.22.2/libexec/src/testing/testing.go:1631 +0x1c4
testing.tRunner.func1()
/opt/homebrew/Cellar/go/1.22.2/libexec/src/testing/testing.go:1634 +0x33c
panic({0x10441d540?, 0x1044455e0?})
/opt/homebrew/Cellar/go/1.22.2/libexec/src/runtime/panic.go:770 +0x124
panicker.Panics(...)
/Users/gwyn/Source/panicker/panicker_test.go:6
panicker.TestPanics(0x14000124680?)
/Users/gwyn/Source/panicker/panicker_test.go:17 +0x30
testing.tRunner(0x14000124680, 0x104444dc0)
/opt/homebrew/Cellar/go/1.22.2/libexec/src/testing/testing.go:1689 +0xec
created by testing.(*T).Run in goroutine 1
/opt/homebrew/Cellar/go/1.22.2/libexec/src/testing/testing.go:1742 +0x318
FAIL    panicker        0.082s
FAIL

Here's the implementation

Update TestPanics to contain a deferred call to recover().

package panicker

import "testing"

func Panics() {
    panic("I panic!")
}

func TestPanics(t *testing.T) {
    defer func() {
        err := recover()
        if err == nil {
            t.Fatalf("Expected panic with invalid address, received %v", err)
        }
    }()

    Panics()
}
        
    

How does she run now?

; go test -v panicker
=== RUN   TestPanics
--- PASS: TestPanics (0.00s)
PASS
ok      panicker        0.123s

Notice the error check condition: recover() is non-nil if it's actually called; if recover()'s return value is nil, it was never triggered, so we didn't actually trigger our expected error.