Recognize a desired state and synchronize on when that state has been achieved.
Let's say you want to write an assertion to verify a simple cause and effect: when a certain button is clicked, a dialog appears containing some text that gets loaded from the network.
In order to do this, you have to make sure that your assertion runs after the effect you're testing has been realized.
If not, then you could end up with a false negative, or "flaky test" because you ran the assertion too early. If you'd only waited a little bit longer, then your test would have passed. So sad!
In fact, test flakiness is the reason most people shy away from writing big tests in JavaScript in the first place. It seems almost impossible to write robust tests without having visibility into the internals of your runtime so that you can manually synchronize on things like rendering and data loading. Unfortunately, those can be a moving target, and worse, they couple you to your framework.
But what if instead of trying to run our assertions at just the right time, we ran them many times until they either pass or we decide to give up?
This is the essence of what @bigtest/convergence
provides:
repeatedly testing for a condition and then allowing code to run when
that condition has been met.
And it isn't just for assertions either. Because it is a general mechanism for synchronizing on any observed state, It can be used to properly time test setup and teardown as well.
A convergent function is a function that runs repeatedly until it no longer returns false or throws an error. When the function is finally successfully executed, it is considered to be passing and a converging promise will resolve. However, if the converging function does not pass within the provided timeout period the promise will reject with the last error thrown from the function.
The when
function allows you to synchronize on a condition
import { when } from '@bigtest/convergence'
when(() => expect($el).to.exist)
.then(() => $el.get(0).click())
Another common pattern is asserting that something has not changed. For these scenarios, you want to detect that _the assertion passes for the duration of a timeout period.
always()
is just like when()
except that the assert
function is
looped over repeatedly until it fails for the first time or never
fails for the duration of the timeout.
// this will resolve when total is always 5
// for the duration of the timeout period
always(() => total === 5)
.then(() => console.log("Yup. definitely 5"));