Explain Codes LogoExplain Codes Logo

How to test the type of a thrown exception in Jest

javascript
error-handling
jest
testing
Nikita BarsukovbyNikita Barsukov·Jan 1, 2025
TLDR

Inspect the type of a thrown exception in Jest using the .toThrow(Type) matcher. Enclose the troublesome piece of code within a function with expect():

expect(() => yourFunction()).toThrow(ExpectedError);

Ensure ExpectedError coincides with the expected error class. This assertion puts yourFunction() to the test, determining if an error of the expected type will be thrown.

Journey through Jest Error Testing

The Magic of toThrow

You could simply test for error types, or you can walk the extra mile and verify the exact error messages for comprehensive error handling:

expect(() => riskyBusiness()).toThrowError(/expected error message/);

Here we're summoning the powers of regular expressions to flag down specific parts of the error message. This adds another layer of flexibility to your error capture net.

Async Error Catching

For dealing with asynchronous code, harness the async/await syntax and introduce the .rejects matcher for proficiently catching expected asynchronous errors:

await expect(asyncOperation()).rejects.toThrow(ExpectedAsyncError);

The "async" in "asynchronous" does not stand for "ignore error handling". Keep testing your async functions and remember to handle both exception types and contentious messages.

Keeping Assertions in Check

Utilize expect.assertions(number) to ensure a designated number of assertions have been called within a test:

test('a balance between order and chaos', () => { expect.assertions(1); expect(() => questionableMethod()).toThrow(PredictableError); });

This archaic ritual keeps your expect statements accountable, ensuring they're not just lazing around - especially when handling asynchronous operations.

The Try/Catch Ball Game

Jest's toThrow matcher is pretty nifty, but sometimes you'll need to get your hands dirty with manual error handling. Enter the try/catch block, empowering you to inspect additional error properties or behaviours:

try { possiblyBrokenFunction(); } catch (e) { // when jest gives you lemons, make some lemony assertions! expect(e).toBeInstanceOf(ExpectedFailure); expect(e).toHaveProperty('message', 'planned chaos'); }

The try/catch block grants you full control over the error handling process. The control freak within you will be delighted!

Extra tips: Testing with style

Environment matters

Your test environment can profoundly influence the type of errors thrown. Strive for consistency across different test and development environments to avoid those despicable flaky tests. For instance, make sure your CI server isn't running a prehistoric Node.js version while your development machine enjoys the latest ECMAScript features.

Avoid false positives

Combining type and message checks in one .toThrow call without carefully considering the implications can lead to false positives. This can lead to tests passing when they, in fact, should be failing. It's like a bad movie getting good reviews. How? Why?! To avoid such travesty, separate them:

// don't mash both assertions into the same expect(() => uncertainOperation()).toThrow(KnownUncertaintyError); expect(() => uncertainOperation()).toThrowError(/We messed up!/);

Tooling issues with ESLint or TypeScript might induce gnashing of teeth and rage-quit tendencies. But courage! Instead of disabling linter or TypeScript checks, consider using intermediate variables or functions and annotate those properly:

function riskyStep() { return riskyActionThatFailsOften(); } // Life is an error-prone business! expect(riskyStep).toThrow(KnownRiskException);

This approach helps prevent type assertion errors and keeps your code linter-friendly. It's like a polite "keep off the grass" sign for those pesky red underlines!