Explain Codes LogoExplain Codes Logo

Can you write async tests that expect toThrow?

javascript
async-testing
error-handling
promise-testing
Nikita BarsukovbyNikita Barsukov·Nov 15, 2024
TLDR
await expect(asyncFunction()).rejects.toThrow('Oops! Houston, we have a problem');

Your guide to expect().rejects.toThrow() in Jest for testing async exceptions. This handy method tests whether async functions fail with specific errors for concise and effective error handling.

Making sense of async error testing

Ensuring your asynchronous functions behave as intended when errors appear is crucial. The async/await syntax is the key to properly executing the tests and handling outcome.

Wrap it up

The trick to capturing async errors is to wrap async calls in an arrow function or define an async function :

// The secure arrow way await expect(() => asyncFunction()).rejects.toThrow('Error with ID 42'); // Or becoming a decorated async officer await expect(asyncFunction).rejects.toThrow('Error with ID 42');

Eyes on the error

Make your tests robust: are they throwing just Errors or custom error classes:

// Looking for a generic 'Error' as if we're in an Error safari await expect(asyncFunction()).rejects.toBeInstanceOf(Error); // Going after specific prey: our 'CustomError' await expect(asyncFunction()).rejects.toBeInstanceOf(CustomError);

And why settle for only the class? Go for the error message or instances:

// Some regex CSI work on the error message await expect(asyncFunction()).rejects.toThrowError(/deadly error lurking somewhere/);

Number of assertions to track

Enter expect.assertions(n) where n is how many assertions you expect in the test. So, no stealthy assertions go unnoticed.

test('async function throws custom error', async () => { expect.assertions(1); // putting a leash on assertions await expect(asyncFunction()).rejects.toBeInstanceOf(CustomError); });

No false positives

Ensure promises don't go to the resolve side causing false positives:

test('promise rejects with an error', async () => { await expect(somePromise()).rejects.toThrow(); // We are NOT resolving here! });

Testing multiple errors simultaneously

Why stop at one? Have multiple checks in a single test:

test('multiple exception checks', async () => { await expect(asyncFunction()).rejects.toMatchObject({ name: 'EvilError', // Getting the villain by its name message: 'I am inevitable', // and it's catch phrase }); });

Don't forget using regular expression for a more flexible match:

// Regex to the rescue when you can't remember the whole phrase await expect(asyncFunction()).rejects.toThrow(/inevitable/i);

Using .catch() without async/await

Using promise with .catch()? No problem, we got you:

somePromise() .then(() => { throw new Error('Whoops! Ended up resolving when we were supposed to reject'); }) .catch(error => expect(error).toBeInstanceOf(ExpectedError)); // The catch wrestling move for promise rejections