How do I unit test asynchronous methods nicely?
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.
Introduction
Testing asynchronous methods cleanly is mostly about making completion deterministic. A good async unit test does not sleep for arbitrary amounts of time and hope for the best; it waits explicitly for the operation to finish and keeps external timing, network calls, and background scheduling under control.
The First Rule: Make Completion Observable
An async method is easiest to test when it exposes a clear completion mechanism such as a promise, an async function, or a callback. Once the test framework can wait for that completion signal, the test becomes much less fragile.
With Jest and async/await, the clean pattern is to await the call directly:
That is much better than calling the method and then sleeping for a fixed delay. The test waits for the actual completion rather than guessing how long the operation might take.
Return the Promise or Await It
A common mistake in async tests is forgetting to return the promise or await it. Then the test can finish before the asynchronous work actually runs.
Correct promise-based style:
Correct async style:
Both are fine. The key is that the test runner must know there is unfinished async work.
Control Dependencies, Not Time
The nicest async tests isolate the method from real time and real network activity. If your method depends on an API client, database call, or message queue, replace that dependency with a controlled fake or mock.
This gives you a fast, reliable test because the dependency resolves immediately and predictably.
Avoid sleep-Style Tests
Bad async tests often look like this idea:
- start the method,
- wait 100 milliseconds,
- then hope the result is ready.
That approach is brittle because:
- it slows the test suite,
- it still can fail on a busy machine,
- and it hides the real completion condition.
If your only way to test a method is by sleeping and polling, the method or its dependencies probably need a better interface.
Test Failures Explicitly
Async code often has both success and failure paths. The failure path should be tested just as directly.
That keeps error handling under test without resorting to log inspection or timing hacks.
Common Pitfalls
- Forgetting to return or await the asynchronous operation in the test.
- Using arbitrary delays instead of waiting for the real completion signal.
- Hitting real network or database dependencies in unit tests.
- Testing only the success case and ignoring rejections or callback errors.
- Writing async code that exposes no clean completion point, which makes tests awkward by design.
Summary
- Async methods are easiest to test when completion is explicit and deterministic.
- Return promises or await them so the test framework knows when the test is actually finished.
- Mock dependencies instead of depending on real time or real external systems.
- Avoid
sleep-style tests because they are slow and fragile. - Test both success and failure paths with the same direct async patterns.
Related reading
- How do I update an ObservableCollection via a worker thread?
- How do I update the GUI from another thread?
- How do I update the GUI from another thread?
- How do I use await inside a generator?
- How do I upload a build to iTunes Connect for TestFlight?
- How do I use Assert to verify that an exception has been thrown with MSTest?
- How do I use threading in Python?
- How do I use threading in Python?
.png&w=3840&q=75)
Tackling System Design Interview Problems
A short course that equips you with the skills to approach system design interviews methodically.
Start the free courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.