Tests are one of the most common places to reach for WaitForIt. Integration and end-to-end
tests routinely have to wait for an asynchronous or concurrent operation to finish before they
can make an assertion — a background job to run, a message to propagate, a record to land in the
database. WaitForIt replaces brittle Process.sleep/1 calls and hand-rolled polling loops with
expressive waits that fail fast and read clearly.
The quickest path: WaitForIt.Test assertions
For most tests, the WaitForIt.Test assertions are the most ergonomic option. They wait and
re-evaluate just like the core macros, but on timeout they fail with a normal
ExUnit.AssertionError — including the source expression and the last value seen — so failures
read like any other ExUnit failure.
defmodule MyApp.SomeTest do
use ExUnit.Case
use WaitForIt.Test
end# Wait until an expression becomes truthy:
assert_eventually Repo.get(User, user_id).confirmed
# Wait until a pattern matches, binding out of it for later assertions:
assert_eventually {:ok, user} = fetch_user(user_id), timeout: 2_000
assert user.first_name == "Elijah"
# Assert something never happens within a window:
refute_eventually error_reported?()
# Assert something holds for the whole window:
assert_always circuit_closed?(), timeout: 500These default to test-friendly timeouts (assert_eventually waits up to 1s; refute_eventually
and assert_always sample for 100ms). See WaitForIt.Test for details.
The rest of this guide covers the core macros directly, which you can also use in tests when you want their exact return values or timeout semantics. The remaining examples assume:
defmodule MyApp.SomeTest do
use ExUnit.Case
import WaitForIt
endAssert on a truthy result with wait/2
wait/2 returns the truthy value that ended the wait, or the last falsy value on timeout, so it
drops straight into an assertion:
assert wait(Repo.get(User, user_id))Because pattern matching also works on the return value, you can make a stronger assertion:
assert %User{first_name: "Elijah"} = wait(Repo.get(User, user_id), timeout: 1_000)Bind a result with match_wait/3
When you are waiting for a tagged result and want the value out of it, match_wait/3 is the most
direct form:
{:ok, user} = match_wait({:ok, %User{}}, Repo.fetch(User, user_id), timeout: 1_000)
assert user.first_name == "Elijah"On timeout match_wait/3 raises a MatchError, which fails the test with the last value it saw.
Branch on the outcome with case_wait/3
case_wait/3 shines when more than one outcome is interesting, or when you want a custom failure:
case_wait Repo.all(active_users_query), timeout: 2_000, interval: 50 do
[only_user] ->
assert only_user.id == 42
[_ | _] = users ->
assert length(users) == 2
else
[] ->
flunk("expected at least one active user, got none")
endThe optional else block runs only on timeout, and can pattern-match on the last value that was
evaluated before giving up.
Tips
- Keep timeouts tight. A wait should be long enough to absorb normal jitter but short enough
that a genuine failure surfaces quickly. Prefer an explicit
:timeoutover the 5-second default in tests. - Tune the interval for fast feedback. A smaller
:interval(e.g.interval: 10) makes a successful wait return sooner; the default of100ms is usually fine. - Reach for the
!variants for crisp failures.wait!/2and friends raiseWaitForIt.TimeoutErrorwith the wait type, timeout, and last value, which can make the cause of a failing test obvious at a glance. - Avoid catch-all clauses in
case_wait/3andcond_wait/2— a clause that always matches ends the wait on the first evaluation. Use anelseblock instead.
Next: Polling vs signaling