-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Improve the behavior of require
assertions when using assert.EventuallyWithT
#1396
Comments
Maintenance work on testify is huge. Please ease work of the maintainers by providing an example running on the Go Playground. Here is a template: https://go.dev/play/p/hZ3USGA87Fk |
Sure, I have created an example to showcase the current behavior (panic): https://go.dev/play/p/YiRjPaHFMr1 |
It seems that some assertions will call `FailNow()` which panics when called on `assert.CollectT`. See: - stretchr/testify#1396 - stretchr/testify#1457
It seems that some assertions will call `FailNow()` which panics when called on `assert.CollectT`. See: - stretchr/testify#1396 - stretchr/testify#1457
This is good improvement on |
For folks trying out the new testify release which includes this change (1.10), I want to point out that while this issue was closed when #1481 was merged, the behavior that was implemented didn't match my original request. Considering the original example: assert.EventuallyWithT(t, func(c *assert.CollectT) {
exist, err := myFunc()
require.NoError(c, err)
assert.True(c, exist)
}, 1*time.Second, 10*time.Millisecond, "Object was not created") The request was for So in testify 1.10.0, assuming While I can't claim that one approach is clearly superior to the other one, I wished the behavior that was originally suggested was implemented. The current behavior can already be obtained with: assert.EventuallyWithT(t, func(c *assert.CollectT) {
exist, err := myFunc()
if !assert.NoError(c, err) {
return
}
assert.True(c, exist)
}, 1*time.Second, 10*time.Millisecond, "Object was not created") while as far as I know, it is not possible to achieve the behavior originally requested. |
I'll have a look at this. |
This is working as designed and the functionality you desire can be achieved without any delays. Consider this test which will panic because you cannot de-reference the nil i: func TestIfy(t *testing.T) {
f := func() (*int, error) {
return nil, errors.New("my hovercraft is full of eels")
}
assert.EventuallyWithT(t, func(c *assert.CollectT) {
i, _ := f()
assert.Equal(c, 1, *i)
}, time.Second, time.Millisecond)
} The reason that CollectT supports require assertions (and calls to CollectT.FailNow) is to allow us to end the condition function early in a way we are familiar with. This version now fails gracefully because the condition function never tries to deference i: func TestIfy(t *testing.T) {
f := func() (*int, error) {
return nil, errors.New("my hovercraft is full of eels")
}
assert.EventuallyWithT(t, func(c *assert.CollectT) {
i, err := f()
require.NoError(c, err)
assert.Equal(c, 1, *i)
}, time.Second, time.Millisecond)
} If in your test, a non-nil err is fatal to the entire test, then you should use the outer *testing.T: func TestIfy(t *testing.T) {
f := func() (*int, error) {
return nil, errors.New("my hovercraft is full of eels")
}
assert.EventuallyWithT(t, func(c *assert.CollectT) {
i, err := f()
require.NoError(t, err) // require with t, not c
assert.Equal(c, 1, *i)
}, 10_000*time.Hour, time.Millisecond)
} This is better because:
|
Thanks for following up @brackendawson. While the proposed pattern does work, in my opinion it suffers from a couple of issues:
However, I feel like these issues could be solved with documentation, specifying that:
I think the above documentation changes would provide confidence that this is a supported / intended pattern, and that it will keep working in future testify releases. |
You are right in that calling FailNow on the outer testing.T from he condition function is unsupported. This could only be remedied by removing the use of a goroutine which requires reverting the initial fix to this issue by changing the CollectT.FailNow implementation back to a panic. While my example works sometimes there isn't a fully safe way to stop a test from the condition function, and there has never been one.
I don't think it is, on further consideration.
I exepect the assert.EventuallyWithT wont fail at all
Execution of the Go test/subtest will be stopped. What to doCollectT.FailNow currently says:
But nothing says the condition function is run in a goroutine, so there is no reason to assume it fails and stops the condition function rather than the test. That behaviour has always been the case, so the documentation and example in EventuallyWithT should be updated to make that clear. I'm going to keep this open until we resolve the documentation issue. If we want a kosher method of stopping the test execution from the condition function then we need a new issue. |
Yes, you're correct, I wasn't very accurate. The assertion doesn't fail, but its execution is stopped.
That's not what I observe at the moment. The Example
Well, personally when I opened this issue I only wanted a way to fail the |
You're right, the test keeps running. I don't think this can be guaranteed or documented here though. It's unsupported. |
Consider the following use of
EventuallyWithT
.Here the intention is to fail
assert.EventuallyWithT
immediately if therequire.NoError
assertions fails. Instead this code will panic ifmyFunc()
returns an error. This is because of how theFailNow
method is implemented on theCollectT
object:testify/assert/assertions.go
Lines 1872 to 1875 in f97607b
The panic ensures that the call to the
condition
function "exits" immediately. However it will immediately interrupt test execution.It feels like the implementation could be improved, so that it immediately fails the
assert.EventuallyWithT
assertion, without crashing the test.There are 2 possible implementations IMO:
FailNow
implementation as follows:The relevant code in
EventuallyWithT
could then look like this:recover
inEventuallyWithT
to recover from the panic. I am not going into too many details here, as I think the other solution is better. Panics can be cause by user code in thecondition
function, and we probably don't want to recover from these.If the new suggested behavior is not acceptable, I'd like to understand the rationale for the current implementation.
cc @tnqn
The text was updated successfully, but these errors were encountered: