Choosing Asynchronous Testing in Jasmine: done(), Promises, or async/await
When testing async JavaScript with Jasmine, decide between done(), promise returns, or async/await. This guide compares patterns, explains trade‑offs, and shows concrete test examples for setTimeout, fetch, and custom promises.
16 Jul 2026, 18:02 UTC

Problem Statement
When unit‑testing JavaScript that performs asynchronous work—such as setTimeout callbacks, XHR requests, or promises—Jasmine must be told when a test has finished. If you let a test finish before the async code runs, you get false positives. If you wait too long or forget to signal completion, tests time out or hang. The core decision is which Jasmine async API to use: the legacy done() callback, returning a promise, or the newer async/await syntax.
Decision Context & Constraints
- Jasmine Version:
2.0+supports promise returns and async functions.1.xonly offersdone(). - Testing Environment: Browsers, Node, or headless runners. All support promises, but older browsers may need a polyfill.
- Readability & Maintainability: Code that closely mirrors production async patterns is easier to audit.
- Error Propagation: Promises and async/await automatically surface rejections;
done.fail()is required fordone(). - Clock Mocking:
jasmine.clock()works with bothdone()and promise returns.
Comparing Async Patterns
| Pattern | Syntax | Typical Use | Error Handling | Readability |
|---|---|---|---|---|
| done() callback | |
Legacy callbacks, setTimeout, XHR callbacks | Explicit done.fail(error) or try/catch |
Verbose, but explicit about completion |
| Return a promise | |
Promised APIs, fetch, AJAX wrappers | Rejections propagate automatically | Concise, mirrors production code |
| async/await | |
Any async function returning a promise | Rejections throw, caught by Jasmine | Fluent, modern syntax |
Trade‑Off Analysis
- done() is the most compatible but requires manual signaling. It’s useful when you have callback‑based APIs that don’t return promises.
- Promise returns keep test code short and let Jasmine handle resolution. However, mixing
done()and a returned promise can cause race conditions. - async/await offers the cleanest syntax but demands that the async operation returns a promise. It also requires a JavaScript engine that supports ES2017 or a transpiler.
- All patterns work with
jasmine.clock().useFakeTimers()for deterministic timing tests.
Concrete Implementation & Validation
Example 1: setTimeout with done()
describe('Timer tests', function() {
it('waits 100ms', function(done) {
let flag = false;
setTimeout(() => { flag = true; }, 100);
setTimeout(() => {
expect(flag).toBe(true);
done();
}, 150);
});
});
Run with jasmine and verify that the test passes after ~150ms. If done() is omitted, the test will time out.
Example 2: Promise return with fetch (polyfilled if needed)
it('fetches data', function() {
return fetch('/api/data')
.then(res => res.json())
.then(data => {
expect(data.id).toBe(42);
});
});
Here Jasmine waits for the promise to resolve. If the promise rejects, the test fails automatically.
Example 3: async/await with a custom promise
function asyncOp() {
return new Promise(resolve => setTimeout(() => resolve('ok'), 50));
}
it('uses async/await', async function() {
const result = await asyncOp();
expect(result).toBe('ok');
});
Notice no done() argument. If asyncOp() throws, Jasmine captures the exception.
Using jasmine.clock()
describe('Fake timers', function() {
beforeAll(() => jasmine.clock().install());
afterAll(() => jasmine.clock().uninstall());
it('advances timers', function(done) {
let called = false;
setTimeout(() => { called = true; }, 1000);
jasmine.clock().tick(1000);
expect(called).toBe(true);
done();
});
});
Clock mocking works regardless of the async pattern. It removes real waiting time and makes tests deterministic.
Validation Checklist
- Run
jasmine --versionto ensure you have2.0+. - Execute the suite above; each test should pass quickly.
- Introduce a deliberate failure (e.g., wrong expectation) and confirm the test fails.
- For
done()tests, comment outdone()and observe a timeout error. - For promise tests, reject the promise and confirm the test fails.
Practical Takeaway
Use async/await when your production code already returns promises; it keeps tests concise and reduces boilerplate. Fall back to done() only for legacy callback APIs that cannot be wrapped. Avoid mixing done() and promise returns in the same test to prevent race conditions. Leverage jasmine.clock() for deterministic timing tests, regardless of the async pattern.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.