4.3 Failing tests programmatically
When testing code with Jest, it can sometimes be useful to fail a test arbitrarily.
This section goes through a few scenarios where that might be useful and how to fail a Jest test explicitly/in a forced manner.
It also presents more idiomatic Jest patterns that could be used interchangeably.
Fail() a synchronous test that should always throw with Jest
Here is our code under test:
function throwOrNot(shouldThrow = false) {
if (shouldThrow) {
throw new Error('shouldThrow was true');
}
return 'success';
}
Creating a naive test that only tests the “happy” path
Here is the naive test, which succeeds if the error is thrown.
it('should throw if passed true', () => {
try {
throwOrNot(true);
} catch (error) {
expect(error).toEqual(new Error('shouldThrow was true'));
}
});
As we can see from the output, the test passes when put into the throw
branch of the test under code.
npx jest src/04.03-naive-synchronous-fail.test.js
PASS src/04.03-naive-synchronous-fail.test.js
✓ should throw if passed true (3ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Imagine we modified throwOrNot
to stop satisfying this test (it doesn’t throw when passed true), the same test still passes.
function throwOrNot(shouldThrow = false) {
if (shouldThrow) {
throw new Error('shouldThrow was true');
}
return 'success';
}
it('should throw if passed true', () => {
try {
throwOrNot(false);
} catch (error) {
expect(error).toEqual(new Error('shouldThrow was true'));
}
});
As per the following test run output, the tests are still passing despite the behaviour not being present any more:
npx jest src/04.03-naive-synchronous-fail.test.js
PASS src/04.03-naive-synchronous-fail.test.js
✓ should throw if passed true (3ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
What we need to do is to make sure the try
block doesn’t continue executing if the throwOrNot
function executes without issue.
Force fail() a synchronous Jest test
What we should try to do is bind the function under test and use toThrow()
, not.toThrow()
assertions instead of failing programmatically.
function throwOrNot() {
return 'success';
}
test('should throw if passed true', () => {
expect(throwOrNot.bind(null)).not.toThrow(
new Error('error')
);
});
Technically failing a test programmatically is as simple as adding a throw new Error('err')
, but having such throw
’s not so idiomatic in Jest.
Fail() an async/await Jest test that should always throw with Jest
We define an async function for which we want to throw
under some condition (here if passed true
when called).
async function asyncThrowOrNot(shouldThrow = false) {
if (shouldThrow) {
throw new Error('shouldThrow was true');
}
return 'success';
}
Much in the same way as for the synchronous throw
example, it’s very easy to write a test that will give false positives.
it('should throw if passed true', async () => {
try {
await asyncThrowOrNot(true);
} catch (error) {
expect(error).toEqual(new Error('shouldThrow was true'));
}
});
One way to arbitrarily fail a Jest test is to throw
an Error
in a branch or line of code that shouldn’t be reached:
async function asyncThrowOrNot() {
return 'success';
}
it('should throw if passed true', async () => {
try {
await asyncThrowOrNot(true);
throw new Error("didn't throw");
} catch (error) {
expect(error).toEqual(new Error('shouldThrow was true'));
}
});
The more idiomatic way to check an async
function throws is to use the await
or return
an expect(fn(param1)).rejects.toEqual(error)
.
Note: make sure to
await
orreturn
theexpect()
expression, otherwise Jest might not see the error as a failure but anUnHandledPromiseRejection
async function asyncThrowOrNot() {
throw new Error('error');
}
test('should throw if passed true return expect()', async () => {
return expect(asyncThrowOrNot()).rejects.toEqual(new Error('error'));
});
test('should throw if passed true await expect()', async () => {
await expect(asyncThrowOrNot()).rejects.toEqual(new Error('error'));
});
npx jest src/04.03-async-throw.test.js
PASS src/04.03-async-throw.test.js
✓ should throw if passed true return expect() (3ms)
✓ should throw if passed true await expect()
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Fail() a synchronous Jest test that shouldn’t throw
By default a synchronous Jest test that shouldn’t throw will fail if it throws:
test('should not throw', () => {
throw new Error('it threw');
});
The following output shows how the test fails when the test throws.
FAIL
✕ should not throw (2ms)
● should not throw
it threw
1 | it('should not throw', () => {
> 2 | throw new Error('it threw');
| ^
3 | });
4 |
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Fail() an async/await Jest test that shouldn’t throw
By default an asynchronous (async/await) Jest test that shouldn’t throw will fail if it throws/rejects:
it('should not top-level throw', async () => {
throw new Error('it threw');
});
it('should not throw on Promise rejection', async () => {
await Promise.reject(new Error('Promise rejection'));
});
it('should not throw on async function throw', async () => {
const throws = async () => {
throw new Error('async-function throw');
};
await throws();
});
The following output shows how the test fails when the test throws.
Note how throw
in an it
callback async
function, await
-ing a Promise rejection and throw
in an await
-ed async function all fail the test.
FAIL
✕ should not top-level throw (3ms)
✕ should not throw on Promise rejection (1ms)
✕ should not throw on async function throw
● should not top-level throw
it threw
1 | it('should not top-level throw', async () => {
> 2 | throw new Error('it threw');
| ^
3 | });
4 | it('should not throw on Promise rejection', async () => {
5 | await Promise.reject(new Error('Promise rejection'));
at Object.<anonymous>
● should not throw on Promise rejection
Promise rejection
3 | });
4 | it('should not throw on Promise rejection', async () => {
> 5 | await Promise.reject(new Error('Promise rejection'));
| ^
6 | });
7 | it('should not throw on async function throw', async () => {
8 | const throws = async () => {
at Object.<anonymous>
● should not throw on async function throw
async-function throw
7 | it('should not throw on async function throw', async () => {
8 | const throws = async () => {
> 9 | throw new Error('async-function throw');
| ^
10 | };
11 |
12 | await throws();
at throws
at Object.throws
Test Suites: 1 failed, 1 total
Tests: 3 failed, 3 total
Jest is Promise-aware, so throw
, rejection is all the same.
A “fail” function
The example show you how to use throw new Error('testingError')
to force fail()
a Jest (and other test library) test.
This works in synchronous and asynchronous (async/await) Jest tests. In the asynchronous case, it’s because Jest is Promise-aware.
In Jest/JavaScript, a fail
functions could be defined as follows (just throws an Error
):
function fail() {
throw new Error('Test was force-failed');
}
The idiomatic way to do this in Jest however is to use expect().toThrow()
in the synchronous case:
expect(fn.bind(null, param1, param2)).toThrow(new Error('specify the error'));
And return/await expect().rejects.toEqual()
in the asynchronous (async/await) case:
return expect(asyncFn(param1, params)).rejects.toThrow(
new Error('specific the error')
);
// or
await expect(asyncFn(param1, params)).rejects.toThrow(
new Error('specific the error')
);
We’ve now seen how to implement a “fail” function in Jest and its modalities.
The next section will be an activity through which we’ll write a recursive program that rewrites a MongoDB query.