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

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.

Jump to table of contents