Skip to main content

How to Test Exceptions, Throw, Rejects in Jest in JavaScript

A critical part of writing robust tests is verifying that your code fails correctly. Jest provides a powerful and expressive API for testing functions that are expected to throw errors. However, the method you use depends on whether the function is synchronous (using throw) or asynchronous (returning a rejected Promise).

This guide will teach you the modern, idiomatic Jest methods for testing both synchronous and asynchronous exceptions, using .toThrow() and the .rejects modifier.

The Core Task: Testing Synchronous throw vs. Asynchronous reject

It's essential to know which type of error you are testing:

  • Synchronous throw: A standard function that directly throws an error.
    function syncThrow() {
    throw new Error('Something went wrong!');
    }
  • Asynchronous reject: An async function or a function that returns a Promise that rejects.
    async function asyncReject() {
    return Promise.reject(new Error('Something went wrong!'));
    }

Jest has a specific, elegant solution for each of these.

To test a synchronous function, you must wrap the code that throws an error inside another function. This prevents the error from crashing your test.

Example of problem:

// ⛔️ INCORRECT: This will throw an unhandled error and fail the test.
expect(syncThrow()).toThrow();

This fails because syncThrow() is called before expect() gets to run, immediately throwing an error and stopping the test.

Solution: wrap the call to the throwing function in an arrow function inside expect().

function syncThrow(message) {
throw new Error(message);
}

test('should throw an error', () => {
// ✅ Correct: Pass an arrow function to expect().
// Jest will call this function and catch the error for you.
expect(() => syncThrow('Oops!')).toThrow();
});

You can also make your assertion more specific by checking the error message, type, or both.

test('throws a specific error message and type', () => {
const errorMessage = 'Something went wrong!';

// Test for the error message (can be a string or regex)
expect(() => syncThrow(errorMessage)).toThrow('Something went wrong!');
expect(() => syncThrow(errorMessage)).toThrow(/went wrong/);

// Test for the error type (class)
expect(() => syncThrow(errorMessage)).toThrow(Error);
});

To test an async function or a function that returns a Promise, you must use the .rejects modifier. This tells Jest to wait for the promise to be rejected and then allows you to make assertions on the reason for the rejection.

Crucially, your test function must be async and you must await the expect statement.

Example of problem:

// ⛔️ INCORRECT: This will not work for async functions.
test('async function should reject', () => {
// This will result in an unhandled promise rejection error.
expect(() => asyncReject()).toThrow();
});

Solution:

async function asyncReject(message) {
return Promise.reject(new Error(message));
}

// Note that the test function is marked `async`.
test('should reject with an error', async () => {
const errorMessage = 'Async error!';

// ✅ Correct: Use `await` and the `.rejects` modifier.
await expect(asyncReject(errorMessage)).rejects.toThrow('Async error!');

// You can also test for the error type.
await expect(asyncReject(errorMessage)).rejects.toThrow(Error);
});
note

The .rejects property is a "magic" modifier that makes the assertion asynchronous and correctly handles the rejected promise.

The Manual try...catch Method (An Alternative)

While the .toThrow() and .rejects matchers are recommended, you can also test exceptions manually using a try...catch block. This can be useful for making multiple, complex assertions on the error object itself.

When using this method, it is a critical best practice to use expect.assertions() to ensure that your assertions in the catch block were actually run.

test('should catch and verify a thrown error', async () => {
// 1. Tell Jest to expect exactly two assertions.
expect.assertions(2);

try {
// 2. Await the function that you expect to reject.
await asyncReject('Manual catch!');
} catch (error) {
// 3. Make assertions on the caught error object.
expect(error).toBeInstanceOf(Error);
expect(error.message).toBe('Manual catch!');
}
});

If asyncReject() were to accidentally succeed instead of rejecting, the catch block would never run. The expect.assertions(2) call would then cause the test to fail, correctly alerting you that the expected error did not occur.

Conclusion

Testing for exceptions is a crucial part of a robust test suite, and Jest provides clear, idiomatic tools for the job.

  • For synchronous functions that use throw, the recommended best practice is to wrap the call in an arrow function: expect(() => myFunc()).toThrow().
  • For asynchronous functions that return a rejected Promise, the recommended best practice is to use async/await with the .rejects modifier: await expect(myAsyncFunc()).rejects.toThrow().
  • While a try...catch block can be used, it is more verbose and requires expect.assertions() to be safe. Prefer the built-in Jest matchers whenever possible.