How to test an asynchronous JavaScript function Promises, Jasmine, PhantomJS
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.
Introduction
Testing asynchronous JavaScript with Jasmine requires explicit signaling so the test runner knows when work is complete. In Promise-based code, flaky tests usually come from missing returns, incorrect done usage, or timers not being controlled. Even in older PhantomJS-based pipelines, reliable async testing is possible with disciplined patterns.
Promise Tests by Returning the Promise
In Jasmine, the cleanest pattern is to return the Promise from the spec.
When you return the Promise, Jasmine waits automatically. No manual callback is required.
Using done for Callback-Style Interop
If the tested path mixes callbacks and Promises, use done carefully.
Always call done.fail on errors, otherwise failures can be swallowed and tests may hang.
Testing Rejections Explicitly
Error paths deserve first-class tests.
This verifies contract behavior and prevents false positives from untested rejection logic.
Working with Timers in Legacy Environments
In PhantomJS-era test stacks, timer behavior can differ from modern headless Chromium setups. Keep timer-dependent code deterministic using Jasmine clock where possible.
This removes real-time waiting and stabilizes CI runs.
Structuring Async Test Utilities
Large suites benefit from helper wrappers for repetitive Promise assertions, timeout handling, and mock setup. Consistent helpers reduce copy-paste mistakes and make failure output easier to interpret.
Also keep each test focused on one async behavior. Combining multiple asynchronous branches in one spec increases flakiness and debugging time.
CI Stability Techniques
Async tests fail in CI when environment timing differs from local machines. Build tests to be timing-agnostic and deterministic.
Recommended practices:
- avoid magic delays such as waiting arbitrary milliseconds
- isolate network by mocking Promise-returning APIs
- assert one async outcome per test
- use explicit timeouts only as failure guards
Example with a mocked async dependency:
By controlling async sources directly, you reduce flaky timing differences between PhantomJS and modern local browsers.
It also improves test runtime.
And makes failures clearer.
Common Pitfalls
A common pitfall is forgetting to return the Promise from a Jasmine spec. The test may pass before assertions run.
Another issue is calling done more than once in complex control flow. This can produce confusing failures.
Developers also mix fake timers with real Promise microtasks without understanding ordering. Keep timer and Promise handling explicit.
Finally, do not rely on arbitrary sleep calls in tests. Deterministic synchronization is faster and more reliable.
Summary
- Return Promises from Jasmine specs whenever possible.
- Use
doneonly when necessary and always handle failure paths. - Test both resolve and reject behavior.
- Control timers for deterministic async tests.
- Keep specs small and focused to reduce flakiness in CI.
Related reading
- How to throw an exception from callback in WCF Async using IAsyncResult
- How to timeout a thread
- How to timeout a thread
- How to trace async/await errors in node.js?
- How to test generator based flow-control in JavaScript ES6?
- How to train a model in nodejs tensorflow.js?
- How to test an SQL Update statement before running it?
- How to test Classes with ConfigurationProperties and Autowired
.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.