gotchatypescriptnoneMajor
Testing Async Code: Avoiding Common Pitfalls
Viewed 0 times
async testPromiseawaitdone callbackrejects.toThrowtest timeout
Error Messages
Problem
Async tests appear to pass even when assertions fail because the test completes before the Promise resolves, or because thrown errors inside Promises are swallowed.
Solution
Always return or await Promises in tests. Use the correct patterns for different async styles.
// BAD: test completes before Promise resolves
test('fetches user', () => {
fetchUser('1').then(user => {
expect(user.name).toBe('Alice'); // Never reached!
});
});
// GOOD: await the Promise
test('fetches user', async () => {
const user = await fetchUser('1');
expect(user.name).toBe('Alice');
});
// Testing rejection
test('throws on missing user', async () => {
await expect(fetchUser('999')).rejects.toThrow('User not found');
});
// Testing callbacks (rare — prefer Promises)
test('calls callback with result', (done) => {
fetchUserCallback('1', (err, user) => {
expect(err).toBeNull();
expect(user.name).toBe('Alice');
done(); // Must call done() or test times out
});
});
// Multiple assertions in parallel
test('all users are valid', async () => {
const users = await fetchUsers();
await Promise.all(
users.map(async user => {
expect(user.id).toBeDefined();
expect(user.email).toMatch(/@/);
})
);
});Why
JavaScript Promises are microtasks — synchronous test code completes before unresolved Promises settle. Without await or return, the test runner marks the test as passed before assertions run.
Gotchas
- Forgetting 'async' on the test function means 'await' syntax works but returns a resolved Promise immediately.
- expect.assertions(N) is useful for ensuring the expected number of assertions ran in callback-style async tests.
- jest.useFakeTimers() affects setTimeout but not real network requests or file I/O.
Revisions (0)
No revisions yet.