diff --git a/require/must.go b/require/must.go new file mode 100644 index 000000000..859641205 --- /dev/null +++ b/require/must.go @@ -0,0 +1,41 @@ +// This file uses generics, which require language version go1.18 or later. +// The go.mod go directive is go1.17 so that testify keeps building on old +// toolchains; a //go:build constraint can only raise the language version on +// Go 1.21 and later, so that is the lowest version this file can be gated on. +//go:build go1.21 + +package require + +import ( + assert "github.com/stretchr/testify/assert" +) + +// Must calls f and returns its value, requiring that f returned a nil error. +// If f returns a non-nil error the test fails immediately, as with [NoError]. +// +// cfg := require.Must(t, func() (Config, error) { return LoadConfig(path) }) +// +// Because the test is stopped with [testing.T.FailNow], Must must be called +// from the goroutine running the test function. +func Must[T any](t TestingT, f func() (T, error), msgAndArgs ...interface{}) T { + if h, ok := t.(tHelper); ok { + h.Helper() + } + v, err := f() + if !assert.NoError(t, err, msgAndArgs...) { + t.FailNow() + } + return v +} + +// Mustf is like [Must] but uses a formatted message. +func Mustf[T any](t TestingT, f func() (T, error), msg string, args ...interface{}) T { + if h, ok := t.(tHelper); ok { + h.Helper() + } + v, err := f() + if !assert.NoErrorf(t, err, msg, args...) { + t.FailNow() + } + return v +} diff --git a/require/must_forward.go b/require/must_forward.go new file mode 100644 index 000000000..2ba849817 --- /dev/null +++ b/require/must_forward.go @@ -0,0 +1,27 @@ +//go:build go1.27 + +package require + +// Must calls f and returns its value, requiring that f returned a nil error. +// If f returns a non-nil error the test fails immediately, as with +// [Assertions.NoError]. +// +// a := require.New(t) +// cfg := a.Must(func() (Config, error) { return LoadConfig(path) }) +// +// Because the test is stopped with [testing.T.FailNow], Must must be called +// from the goroutine running the test function. +func (a *Assertions) Must[T any](f func() (T, error), msgAndArgs ...interface{}) T { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Must(a.t, f, msgAndArgs...) +} + +// Mustf is like [Assertions.Must] but uses a formatted message. +func (a *Assertions) Mustf[T any](f func() (T, error), msg string, args ...interface{}) T { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Mustf(a.t, f, msg, args...) +} diff --git a/require/must_forward_test.go b/require/must_forward_test.go new file mode 100644 index 000000000..5cdb9e200 --- /dev/null +++ b/require/must_forward_test.go @@ -0,0 +1,47 @@ +//go:build go1.27 + +package require + +import ( + "errors" + "testing" +) + +func TestMustWrapper(t *testing.T) { + mockT := new(MockT) + require := New(mockT) + + if v := require.Must(func() (int, error) { return 42, nil }); v != 42 { + t.Errorf("Must returned %d, expected 42", v) + } + if mockT.Failed { + t.Error("Must should not have failed the test") + } + + // MockT.FailNow does not stop execution, so Must returns normally here. + mockT = new(MockT) + require = New(mockT) + require.Must(func() (int, error) { return 0, errors.New("boom") }) + if !mockT.Failed { + t.Error("Must should have failed the test") + } +} + +func TestMustfWrapper(t *testing.T) { + mockT := new(MockT) + require := New(mockT) + + if v := require.Mustf(func() (string, error) { return "ok", nil }, "loading %s", "config"); v != "ok" { + t.Errorf("Mustf returned %q, expected \"ok\"", v) + } + if mockT.Failed { + t.Error("Mustf should not have failed the test") + } + + mockT = new(MockT) + require = New(mockT) + require.Mustf(func() (string, error) { return "", errors.New("boom") }, "loading %s", "config") + if !mockT.Failed { + t.Error("Mustf should have failed the test") + } +} diff --git a/require/must_test.go b/require/must_test.go new file mode 100644 index 000000000..bfba9df86 --- /dev/null +++ b/require/must_test.go @@ -0,0 +1,43 @@ +//go:build go1.21 + +package require + +import ( + "errors" + "testing" +) + +func TestMust(t *testing.T) { + mockT := new(MockT) + + if v := Must(mockT, func() (int, error) { return 42, nil }); v != 42 { + t.Errorf("Must returned %d, expected 42", v) + } + if mockT.Failed { + t.Error("Must should not have failed the test") + } + + // MockT.FailNow does not stop execution, so Must returns normally here. + mockT = new(MockT) + Must(mockT, func() (int, error) { return 0, errors.New("boom") }) + if !mockT.Failed { + t.Error("Must should have failed the test") + } +} + +func TestMustf(t *testing.T) { + mockT := new(MockT) + + if v := Mustf(mockT, func() (string, error) { return "ok", nil }, "loading %s", "config"); v != "ok" { + t.Errorf("Mustf returned %q, expected \"ok\"", v) + } + if mockT.Failed { + t.Error("Mustf should not have failed the test") + } + + mockT = new(MockT) + Mustf(mockT, func() (string, error) { return "", errors.New("boom") }, "loading %s", "config") + if !mockT.Failed { + t.Error("Mustf should have failed the test") + } +}