Testing Asynchronous Code in Jest
19 min read·Jan 1, 2025
In Jest, testing asynchronous code essentially consists in testing the value of a resolved or rejected promise or the value passed as argument of a callback function.
Testing returned promises
To test the value returned by a fulfilled promise, you can return the Promise object itself and declare your assertions within the callback function of its then() method:
it('should return a fulfilled promise', () => {
return promise.then(result => {
expect(result).matcher(value);
});
});
Where:
promiseis an instance of thePromiseclass.
Notes:
- If the promise is rejected, the test will automatically fail.
- If the
returnstatement is omitted, your test will complete before the promise resolves.
Example
In this example, the fetchUser() function returns a fulfilled promise containing an object describing a user:
function fetchUser(id) {
const users = {
1: {
name: 'John Doe',
email: 'john.doe@mail.com'
}
};
const user = users[id];
return user ? Promise.resolve(user) : Promise.reject(new Error('user_not_found'));
}
it('should return a fulfilled promise', () => {
const user = {
name: 'John Doe',
email: 'john.doe@mail.com'
};
return fetchUser(1).then(data => {
expect(data).toEqual(user);
});
});
The short syntax
To test the value returned by a fulfilled promise using the short-hand syntax, you can directly return the expect statement and chain it to the resolves matcher:
it('should return a fulfilled promise', () => {
return expect(promise).resolves.matcher(value);
});
On the other hand, to test the value of a rejected promise, you can use the rejects matcher instead:
it('should return a rejected promise', () => {
return expect(promise).rejects.matcher(value);
});
Example
In this example, the fetchUser() function returns a rejected promise containing an Error object:
function fetchUser(id) {
const users = {
1: {
name: 'John Doe',
email: 'john.doe@mail.com'
}
};
const user = users[id];
return user ? Promise.resolve(user) : Promise.reject(new Error('user_not_found'));
}
it('should return a rejected promise', () => {
return expect(fetchUser(2)).rejects.toStrictEqual(new Error('user_not_found'));
});
Testing awaited promises
To check the value returned by a fulfilled promise, you can declare an asynchronous callback function using the async keyword, and wait for the unit you want to test to complete using the await keyword:
it('should return a fulfilled promise', async () => {
const result = await promise;
expect(result).matcher(value);
});
On the other hand, to check the value returned by a rejected promise, you can wrap the test in a try...catch block:
it('should return a rejected promise', async () => {
try {
await promise;
} catch(error) {
expect(error).toMatch(value);
}
});
Example
In this example, the fetchUser() function returns a fulfilled promise containing an object describing a user:
function fetchUser(id) {
const users = {
1: {
name: 'John Doe',
email: 'john.doe@mail.com'
}
};
const user = users[id];
return user ? Promise.resolve(user) : Promise.reject(new Error('user_not_found'));
}
it('should return a rejected promise', async () => {
const user = {
name: 'John Doe',
email: 'john.doe@mail.com'
};
const result = await fetchUser(1);
expect(result).toEqual(user);
});
The short syntax
To test the value returned by a fulfilled promise, you can also await the expect() statement and chain it to the resolves matcher:
it('should return a fulfilled promise', async() => {
await expect(promise).resolves.matcher(value);
});
On the other hand, to test the value returned by a rejected promise, you can use the rejects matcher instead:
it('should return a rejected promise', async() => {
await expect(promise).rejects.matcher(value);
});
Example
In this example, the fetchUser() function returns a rejected promise containing an Error object:
function fetchUser(id) {
const users = {
1: {
name: 'John Doe',
email: 'john.doe@mail.com'
}
};
const user = users[id];
return user ? Promise.resolve(user) : Promise.reject(new Error('user_not_found'));
}
it('should return a rejected promise', async () => {
await expect(fetchUser(2)).rejects.toEqual(new Error('user_not_found'));
});
Testing callback functions
By default, the execution of a test will complete as soon as the execution of the main function completes, before ever calling the callback.
To prevent this from happening, you can pass a single argument to the test's callback function called done(), and invoke it at the end of the callback's execution:
it('should wait for the callback execution', (done) => {
function cb(data) {
expect(data).matcher(value);
done();
}
fn(cb);
});
Notes:
If the
done()function is never called, the test will fail with a timeout error.If the
expect()statement fails, it will throw an error that must be wrapped in atry...catchblock and passed as argument of thedone()handler, as it will otherwise end up in an opaque timeout error that doesn't show what value was received by theexpect()statement:it('should wait for the callback execution', (done) => { function cb(data) { try { expect(data).matcher(value); done(); } catch(error) { done(error); } } fn(cb); });
Example
In this example, the fetchUser() function executes the callback() function passed as argument with an object containing user information or undefined, and an Error object or undefined:
function fetchUser(id, callback) {
const users = {
1: {
name: 'John Doe',
email: 'john.doe@mail.com'
}
};
const user = users[id];
const error = !user && new Error('user_not_found') || undefined;
callback(user, error);
}
it('should wait for the callback execution', (done) => {
function processUser(data, error) {
if (error) {
done(error);
return;
}
try {
expect(data).toEqual({
name: 'John Doe',
email: 'john.doe@mail.com'
});
done();
} catch(error) {
done(error);
}
}
fetchUser(1, processUser);
});
Summary
Here's a summary of what you've learned in this lesson:
- The
resolvesmatcher is used to test the value of a fulfilled promise. - The
rejectsmatcher is used to test the value of a rejected promise. - The
done()function is used to indicate the end of execution of a callback function.
Enjoying the courses?
I've made these courses completely free so anyone can learn from them. If they've helped you and you'd like to actively support the work behind BackendBrewery, you can leave a tip:
Support BackendBrewery