Learn the basics of how to test with Jest or Vitest. This course covers the main features for writing assertions, tests, mocks, async testing, and more
Learn the basics of how to test with Jest or Vitest. This course covers the main features for writing assertions, tests, mocks, async testing, and more
Testing Fundamentals: Testing Exceptions and Errors
As much as we would love to pretend our code never throws an error, they often do. And it is very important to remember to test for this in tests.
(It is often known as testing the 'unhappy path').
In Jest or Vitest it is quite simple to test, although you often have to wrap your code in another function to assert that an error was thrown.
(Otherwise the entire test will just fail).
Simple example of a function that throws
If you have a function that throws an error, and you need to check that happens then you can pass it to expect(), and add a .toThrow()
Note: in this example I'm passing the function itself- I am not calling it.
Jest or Vitest will see that it is a function, and when combined with .toThrow() it will run the function and check if it was thrown.
This is a little unrealistic, as you will often need to pass arguments. We will get to that!
code
functionalwaysThrows(){thrownewError('This is always thrown');}test('always throws an error',()=>{// note: this is not calling alwaysThrows()expect(alwaysThrows).toThrow('This is always thrown');});
Testing a function that throws with arguments
It is much more likely you need to pass arguments to a function.
To check if it throws, wrap the call in a function and pass that wrapper function.
code
functionmaybeThrows(shouldWeThrow){if(shouldWeThrow)thrownewError('oh no');}test('it throws when passed a value',()=>{expect(()=>maybeThrows(true)).toThrow('oh no');// which is the same as:constfn=()=>maybeThrows(true);expect(fn).toThrow('oh no');});
Matching the type of error
You don't actually have to pass anything to toThrow().
This will pass
code
test('it throws when passed a value',()=>{expect(()=>maybeThrows(true)).toThrow();});
But sometimes you need to check the exact thing that was thrown.
Matching the class that was thrown
If you throw different classes such as class SyntaxError, the easiest way is to do toThrow(SyntaxError):
code
classSyntaxErrorextendsError{}classLengthErrorextendsError{}functioncheckCode(code){if(code ==='syntax-error')thrownewSyntaxError();if(code.length>5)thrownewLengthError();}test('throws a syntax error',()=>{expect(()=>checkCode('syntax-error')).toThrow(SyntaxError);});test('throws a length error',()=>{expect(()=>checkCode('very long string')).toThrow(LengthError);});
Matching error strings
You can also pass in a string and it will match it against the Error message
code
functioncheckCode(code){if(code ==='syntax-error')thrownewError('You have a syntax error');if(code.length>5)thrownewError('Too long');}test('throws a syntax error',()=>{expect(()=>checkCode('syntax-error')).toThrow('You have a syntax error');});test('throws a length error',()=>{expect(()=>checkCode('very long string')).toThrow('Too long');});
If doing string comparisons, you can also pass a substring, so these will also pass:
code
functioncheckCode(code){if(code ==='syntax-error')thrownewError('You have a syntax error');if(code.length>5)thrownewError('Too long');}test('throws a syntax error',()=>{expect(()=>checkCode('syntax-error')).toThrow('syntax');});test('throws a length error',()=>{expect(()=>checkCode('very long string')).toThrow('long');});
Regex matches
Because substring matches are straightforward (see previous examples), regex is rarely required. But for more complex patterns you can easily pass in a regex.
code
test('throws error with complex regex pattern',()=>{expect(()=>checkCode('syntax-error')).toThrow(/You have a \w+ error/);});
Testing errors for async behaviour
We will cover this in more detail in a lesson, but if you want to know now, here is an example. You use await before the expect(), and then .rejects.toThrow(YourError)
code
asyncfunctionfetchUserData(userId){if(!userId){thrownewError('User ID is required');}}test('should throw error for missing user ID',async()=>{awaitexpect(fetchUserData()).rejects.toThrow('User ID is required');});
Lesson Task
There are a bunch of tests that need completing. You will need to call the functions which may or may not throw an error, and assert .toThrow('some error') on them.
Remember you often need to wrap the function call in another function (so Jest/Vitest can catch the error)
For the final incomplete test, make sure a specific error prototype is thrown. For this you can pass in .toThrow(SomeError).