testify
testify copied to clipboard
Better alternative to Eventually()
I've found that Eventually
is not really useful:
- I can't use assertions inside
condition
. - Error message doesn't help to understand failure cause:
Condition never satisfied
.
So I use func WaitFor
instead: gist.
The only disadvantage compared to Eventually
:
-
WaitFor
will not stop after timeout (immediately). It will call function one more time to pass error message from assertions inside that function to the test.
// WaitFor calls fn (once in tick) until it passes or timeout expired.
//
// Attempt is failed if any of following methods called in fn:
// - Fail/FailNow
// - Error/Errorf
// - Fatal/Fatalf
// Otherwise attempt is considered as successful and WaitFor stops.
//
// WaitFor receives test object as the first argument.
// It is passed to fn only on the last attempt. All other attempts use fakeT.
// So the test will fail only after timeout expired.
//
// Note: t.Log() and other methods will print to stdout only on the last attempt.
func WaitFor(t TestingT, timeout time.Duration, tick time.Duration, fn func(t TestingT)) {
timer := time.NewTimer(timeout)
ticker := time.NewTicker(tick)
defer timer.Stop()
defer ticker.Stop()
for {
ft := &fakeT{name: t.Name()}
didPanic := false
func() {
defer func() {
if recover() != nil {
didPanic = true
}
}()
fn(ft)
}()
if !ft.Failed() && !didPanic {
return
}
select {
case <-timer.C:
fn(t)
return
case <-ticker.C:
}
}
}
The rest code (with examples) can be found in this gist.
It would be great to see the same feature in testify.
I'd also like to have this.
@maratori Do you happen to have put this in some other public package in the meantime where this would be ready for use now?
@julianbrost It is used in several repos, but they are private.
I'm currently running into this same need. In particular, I have lots of tests that take this form:
PerformAnAction()
data := GetDataForValidation()
//Contains *multiple* assertions of varying types
ValidateTheData(data)
Some of these tests require polling behavior. It would be nice to be able to recover from failed assertions so I can re-run the second and third lines on a timer. Currently, I have to resort to either panic
and recover
or classic error
return values and bypass assert
entirely.
I think the most flexible solution might be to change the signature of condition
to func(Assertions) bool
, so that Eventually
can pass a special "soft" assertion object to the condition. Failures from the soft assertion object would cause a retry, and only failures on the final pass would get turned into "hard" failures.
Actually, looking at the way this library tests its own asserts by giving it a dummy testing.T
:
assert := New(new(testing.T))
It shouldn't be too hard to do the same thing with a custom testing.T
implementation, right? It could serve up a list of all of the inputs to its Errorf
and allow us to replay them onto the real testing.T
afterwards. I'll take a look at implementing that locally.
This is now assert.EventuallyWithT
.