-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Add Must & Mustf #1945
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Add Must & Mustf #1945
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't mix
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Whoops! Missed this, sorry. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @FGasper I've experimented with a few implementations on my fork since your proposals. We all agree that this feature is all about providing a syntactic sugar to replace value, err := fn(args...)
require.NoError(t, err)
...by something more concise and that better conveys the intent to use the value. Your proposal comes a bit short of that as soon as the function takes some parameters: value := require.Must(t, func() (string, error) { return fn(args...) })
...Not much of an improvement, uh? I have tried yet another way: func Must[V any](t TestingT, msgAndArgs ...any) func() (V, error) {
....
}And usage looks like: value := require.Must[int](t)(strconv.Atoi("1"))
...or as a "forward" method: myTest := require.New(t)
value := myTest.Must[int]()(strconv.Atoi("1"))
...which arguably look better (I can inline the function arguments), but requires an explicit type hint. value := require.Must(t)(strconv.Atoi("1"))
...A bit disappointing. This kind of mitigated my initial enthusiasm to add this (at least to the fork I am maintaining, which has full support for generics and generic methods). Further, this helper is decidedly suited for require and doesn't make much sense with assert (or would it be called Maybe I missed something - I'd be happy to try alternate ways, but for now I am a bit short of brighter ideas that could make the helper, actually useful. Any thoughts? |
||
| 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 | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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...) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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") | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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") | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably better to gate this on 1.27 too, like in require.Assertions.Must
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why so, when 1.21 can run the static functions?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it might be confusing to someone using 1.21-1.26 to find that it's available under require but not available under require.Assertions.
I could probably be sold on not changing this part.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add it here just for 1.27, and seeing if there’s a demand for static-only usage in prior.