/ #javascript #jest 

Jest explicitly or arbitrarily force fail() a test

When testing code with Jest, it can sometimes be useful to fail a test arbitrarily.

This post 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.

Table of Contents

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'));
  }
});

To run this example, see Running the examples to get set up, then run:

yarn test src/naive-throws-synchronous-passes.test.js

As we can see from the output, the test passes when put into the throw branch of the test under code.

PASS src/naive-throws-synchronous-passes.test.js
  ✓ should throw if passed true (3ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.22s, estimated 2s
Ran all test suites matching /src\/naive-throws-synchronous-passes.test.js/i.

Imagine we modified throwOrNot to stop satisfying this test (it doesn’t throw when passed true), the same test still passes.

function throwOrNot() {
  return 'success';
}

it('should throw if passed true', () => {
  try {
    throwOrNot(true);
  } catch (error) {
    expect(error).toEqual(new Error('shouldThrow was true'));
  }
});

To run this example, see Running the examples to get set up, then run:

yarn test src/naive-throws-synchronous-false-positive.test.js

As per the following test run output, the tests are still passing despite the behaviour not being present any more:

PASS src/naive-throws-synchronous-false-positive.test.js
  ✓ should throw if passed true (1ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.762s
Ran all test suites matching /src\/naive-throws-synchronous-false-positive.test.js/i.

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

function throwOrNot() {
  return 'success';
}

it('should throw if passed true', () => {
  try {
    throwOrNot(true);
    throw new Error("didn't throw");
  } catch (error) {
    expect(error).toEqual(new Error('shouldThrow was true'));
  }
});

To run this example, see Running the examples to get set up, then run:

yarn test src/fail-throws-synchronous.test.js

Output of the test run shows that if the code doens’t throw, the test suite will fail, which is desired behaviour:

FAIL src/fail-throws-synchronous.test.js
  ✕ should throw if passed true (9ms)

  ● should throw if passed true

    expect(received).toEqual(expected) // deep equality

    Expected: [Error: shouldThrow was true]
    Received: [Error: didn't throw]

       8 |     throw new Error("didn't throw");
       9 |   } catch (error) {
    > 10 |     expect(error).toEqual(new Error('shouldThrow was true'));
         |                   ^
      11 |   }
      12 | });
      13 |

      at Object.toEqual (src/fail-throws-synchronous.test.js:10:19)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        1.883s
Ran all test suites matching /src\/fail-throws-synchronous.test.js/i.

Idiomatic Jest, fail() alternative: check a function throws using the .toThrow Jest matcher

function throwOrNot() {
  return 'success';
}

it('should throw if passed true', () => {
  expect(throwOrNot.bind(null, true)).toThrow(
    new Error('shouldThrow was true')
  );
});

To run this example, see Running the examples to get set up, then run:

yarn test src/fail-throws-synchronous-to-throw.test.js

As in the previous example, the test fails since the code under test doesn’t throw, but this time we get a Received function did not throw error, which is maybe more descriptive and shows the advantage of using the Jest .toThrow matcher.

FAIL src/fail-throws-synchronous-to-throw.test.js
  ✕ should throw if passed true (5ms)

  ● should throw if passed true

    expect(received).toThrow(expected)

    Expected message: "shouldThrow was true"

    Received function did not throw

      4 |
      5 | it('should throw if passed true', () => {
    > 6 |   expect(throwOrNot.bind(null, true)).toThrow(
        |                                       ^
      7 |     new Error('shouldThrow was true')
      8 |   );
      9 | });

      at Object.toThrow (src/fail-throws-synchronous-to-throw.test.js:6:39)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        1.795s
Ran all test suites matching /src\/fail-throws-synchronous-to-throw.test.js/i.

Fail() an async/await Jest test that should always throw with Jest

Creating a naive test that only tests the “happy” path

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';
}

The following test does actually test that the code under test behaves as expected (when it does work as expected).

it('should throw if passed true', async () => {
  try {
    await asyncThrowOrNot(true);
  } catch (error) {
    expect(error).toEqual(new Error('shouldThrow was true'));
  }
});

To run this example, see Running the examples to get set up, then run:

yarn test src/naive-throws-asynchronous-passes.test.js

The output of the test works with a correct implementation:

PASS src/naive-throws-asynchronous-passes.test.js
  ✓ should throw if passed true (4ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.724s, estimated 2s
Ran all test suites matching /src\/naive-throws-asynchronous-passes.test.js/i.

Imagine we modified asyncThrowOrNot to stop satisfying this test (it doesn’t throw when passed true), the same test still passes.

async function asyncThrowOrNot() {
  return 'success';
}

it('should throw if passed true', async () => {
  try {
    await asyncThrowOrNot(true);
  } catch (error) {
    expect(error).toEqual(new Error('shouldThrow was true'));
  }
});

To run this example, see Running the examples to get set up, then run:

yarn test src/naive-throws-asynchronous-false-positive.test.js

Tests are still passing, despite the code not doing what it’s supposed to (throwing), this is a false positive:

PASS src/naive-throws-asynchronous-false-positive.test.js
  ✓ should throw if passed true (2ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.341s, estimated 2s
Ran all test suites matching /src\/naive-throws-asynchronous-false-positive.test.js/i.

As in the previous section, we need to do is to make sure the try block doesn’t continue executing if the asyncThrowOrNot function executes without issue.

Force fail() an asynchronous Jest test

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'));
  }
});

To run this example, see Running the examples to get set up, then run:

yarn test src/fail-throws-asynchronous.test.js

Output shows the test isn’t passing any more (as is expected) but the error message is a bit cryptic Expected: [Error: shouldThrow was true] Received: [Error: didn't throw].

didn't throw happens to be the message we added after await-ing the function under test (see throw new Error("didn't throw");).

FAIL src/fail-throws-asynchronous.test.js
  ✕ should throw if passed true (7ms)

  ● should throw if passed true

    expect(received).toEqual(expected) // deep equality

    Expected: [Error: shouldThrow was true]
    Received: [Error: didn't throw]

       8 |     throw new Error("didn't throw");
       9 |   } catch (error) {
    > 10 |     expect(error).toEqual(new Error('shouldThrow was true'));
         |                   ^
      11 |   }
      12 | });
      13 |

      at Object.toEqual (src/fail-throws-asynchronous.test.js:10:19)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        1.203s, estimated 2s
Ran all test suites matching /src\/fail-throws-asynchronous.test.js/i.

Idiomatic Jest, fail() alternative: check an async function throws using expect().rejects.toEqual

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 or return the expect() expression, otherwise Jest might not see the error as a failure but an UnHandledPromiseRejection

async function asyncThrowOrNot() {
  return 'success';
}

it('should throw if passed true return expect()', async () => {
  return expect(asyncThrowOrNot(true)).rejects.toEqual(
    new Error('shouldThrow was true')
  );
});

it('should throw if passed true await expect()', async () => {
  await expect(asyncThrowOrNot(true)).rejects.toEqual(
    new Error('shouldThrow was true')
  );
});

To run this example, see Running the examples to get set up, then run:

yarn test src/fail-throws-asynchronous-rejects-to-equal.test.js
FAIL src/fail-throws-asynchronous-rejects-to-equal.test.js
  ✕ should throw if passed true return expect() (5ms)
  ✕ should throw if passed true await expect() (1ms)

  ● should throw if passed true return expect()

    expect(received).rejects.toEqual()

    Received promise resolved instead of rejected
    Resolved to value: "success"

      4 |
      5 | it('should throw if passed true return expect()', async () => {
    > 6 |   return expect(asyncThrowOrNot(true)).rejects.toEqual(
        |          ^
      7 |     new Error('shouldThrow was true')
      8 |   );
      9 | });

      at expect (node_modules/expect/build/index.js:138:15)
      at Object.expect (src/fail-throws-asynchronous-rejects-to-equal.test.js:6:10)

  ● should throw if passed true await expect()

    expect(received).rejects.toEqual()

    Received promise resolved instead of rejected
    Resolved to value: "success"

      10 |
      11 | it('should throw if passed true await expect()', async () => {
    > 12 |   await expect(asyncThrowOrNot(true)).rejects.toEqual(
         |         ^
      13 |     new Error('shouldThrow was true')
      14 |   );
      15 | });

      at expect (node_modules/expect/build/index.js:138:15)
      at Object.expect (src/fail-throws-asynchronous-rejects-to-equal.test.js:12:9)

Test Suites: 1 failed, 1 total
Tests:       2 failed, 2 total
Snapshots:   0 total
Time:        1.646s, estimated 2s
Ran all test suites matching /src\/fail-throws-asynchronous-rejects-to-equal.test.js/i.

Fail() a synchronous Jest test that shouldn’t throw

By default a synchronous Jest test that shouldn’t throw will fail if it throws:

it('should not throw', () => {
  throw new Error('it threw');
});

To run this example, see Running the examples to get set up, then run:

yarn test src/synchronous-throw-fail.test.js

The following output shows how the test fails when the test throws.

FAIL src/synchronous-throw-fail.test.js
  ✕ should not throw (2ms)

  ● should not throw

    it threw

      1 | it('should not throw', () => {
    > 2 |   throw new Error('it threw');
        |         ^
      3 | });
      4 |

      at Object.<anonymous> (src/synchronous-throw-fail.test.js:2:9)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        1.172s, estimated 2s
Ran all test suites matching /src\/synchronous-throw-fail.test.js/i.

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();
});

To run this example, see Running the examples to get set up, then run:

yarn test src/asynchronous-throw-fail.test.js

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 src/asynchronous-throw-fail.test.js
  ✕ 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> (src/asynchronous-throw-fail.test.js:2:9)

  ● 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> (src/asynchronous-throw-fail.test.js:5:24)

  ● 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 (src/asynchronous-throw-fail.test.js:9:11)
      at Object.throws (src/asynchronous-throw-fail.test.js:12:9)

Test Suites: 1 failed, 1 total
Tests:       3 failed, 3 total
Snapshots:   0 total
Time:        1.304s
Ran all test suites matching /src\/asynchronous-throw-fail.test.js/i.

Jest is Promise-aware, so throw, rejection is all the same.

Running the examples

Clone github.com/HugoDF/jest-force-fail.

Run yarn install or npm install (if you’re using npm replace instance of yarn with npm run in commands).

Conclusion

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'));

Further Reading

About async functions and the internals of that, I’ve written a longer post: Async JavaScript: history, patterns and gotchas

unsplash-logoAsa Rodger

Looking for a new job? Take Triplebyte’s quiz and have top tech companies pitch you!

Author

Hugo Di Francesco

A Software Engineer who is big on Node.js, queues and Vue(s). Co-author of "Professional JavaScript" with Packt. He shares practical JavaScript tips for the developer who wants to get things done on Code with Hugo. University College London (UCL), MEng Mathematical Computation Graduate.

Get Testing Superpowers with these Underused Jest Features

Subscribe for free resources that turbocharge your Jest tests and a discount on the "Advanced Jest Handbook"