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!
function alwaysThrows() {
throw new Error(
'This is always thrown'
);
}
test('always throws an error', () => {
expect(alwaysThrows).toThrow(
'This is always thrown'
);
});
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.
function maybeThrows(shouldWeThrow) {
if (shouldWeThrow)
throw new Error('oh no');
}
test('it throws when passed a value', () => {
expect(() =>
maybeThrows(true)
).toThrow('oh no');
const fn = () => maybeThrows(true);
expect(fn).toThrow('oh no');
});
You don't actually have to pass anything to toThrow()
.
This will pass
test('it throws when passed a value', () => {
expect(() =>
maybeThrows(true)
).toThrow();
});
But sometimes you need to check the exact thing that was thrown.
If you throw different classes such as class SyntaxError
, the easiest way is to do toThrow(SyntaxError)
:
class SyntaxError extends Error {}
class LengthError extends Error {}
function checkCode(code) {
if (code === 'syntax-error')
throw new SyntaxError();
if (code.length > 5)
throw new LengthError();
}
test('throws a syntax error', () => {
expect(() =>
checkCode('syntax-error')
).toThrow(SyntaxError);
});
test('throws a length error', () => {
expect(() =>
checkCode('very long string')
).toThrow(LengthError);
});
You can also pass in a string and it will match it against the Error message
function checkCode(code) {
if (code === 'syntax-error')
throw new Error(
'You have a syntax error'
);
if (code.length > 5)
throw new Error('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:
function checkCode(code) {
if (code === 'syntax-error')
throw new Error(
'You have a syntax error'
);
if (code.length > 5)
throw new Error('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');
});
Because substring matches are straightforward (see previous examples), regex is rarely required. But for more complex patterns you can easily pass in a regex.
test('throws error with complex regex pattern', () => {
expect(() =>
checkCode('syntax-error')
).toThrow(/You have a \w+ error/);
});
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)
async function fetchUserData(userId) {
if (!userId) {
throw new Error(
'User ID is required'
);
}
}
test('should throw error for missing user ID', async () => {
await expect(
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)
.