510

I'm working with some code where I need to test the type of an exception thrown by a function (is it TypeError, ReferenceError, etc.?).

My current testing framework is AVA and I can test it as a second argument t.throws method, like here:

it('should throw Error with message \'UNKNOWN ERROR\' when no params were passed', (t) => {
  const error = t.throws(() => {
    throwError();
  }, TypeError);

  t.is(error.message, 'UNKNOWN ERROR');
});

I started rewriting my tests in Jest and couldn't find how to easily do that. Is it even possible?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
bartsmykla
  • 5,271
  • 2
  • 10
  • 6

23 Answers23

732

In Jest you have to pass a function into expect(function).toThrow(<blank or type of error>).

Example:

test("Test description", () => {
  const t = () => {
    throw new TypeError();
  };
  expect(t).toThrow(TypeError);
});

Or if you also want to check for error message:

test("Test description", () => {
  const t = () => {
    throw new TypeError("UNKNOWN ERROR");
  };
  expect(t).toThrow(TypeError);
  expect(t).toThrow("UNKNOWN ERROR");
});

If you need to test an existing function whether it throws with a set of arguments, you have to wrap it inside an anonymous function in expect().

Example:

test("Test description", () => {
  expect(() => {http.get(yourUrl, yourCallbackFn)}).toThrow(TypeError);
});
PeterDanis
  • 8,210
  • 2
  • 13
  • 23
  • 9
    Good one - may I ask why the anonimous function wrapper is needed? With the wrapper it works but not without it. – rags2riches-prog Sep 16 '20 at 08:15
  • 58
    @rags2riches the anonymous function is required because `expect(x).toThrow()` requires `x` to be a reference to a function that throws. If you instead pass `expect(x()).toThrow()`, JavaScript will resolve `x()`, which would likely cause the error immediately, and most likely fail your test. – Scott Chow Sep 24 '20 at 16:17
  • The snippet provided in the question is checking a property of the thrown exception. This answer solves only 50% of the problem. Unavoidably we will have to use a `try-catch` block. – Mason Feb 09 '21 at 10:23
  • 1
    @Mason - the person asking the question wanted to check the type of error (" I need to test the type of an exception thrown by a function..."), but I think its good idea to also include check for error message for those wanting to check that. – PeterDanis Feb 09 '21 at 13:26
  • @PeterDanis Peter I wanted to ask you what happens if function t inside the test is an async function. Because I am trying it and it won't pass the test if I expect an Error to be thrown. – Gerasimos Ragavanis Jun 22 '21 at 07:59
  • If you need to pass parameters to your function (which throws the error conditional), you can use `t.bind(t, ...params)`. – marcel Jul 20 '21 at 09:45
  • 37
    @GerasimosRagavanis when you're testing an async function you should `await expect(yourAsyncFn(...)).rejects.toThrow(...)`, like answered by Douglas Caina [here](https://stackoverflow.com/a/68557059/2099911). – Rafael Leite Sep 15 '21 at 17:53
  • @RafaelLeite thank you, even though it is 3 months ago :P I have already found the same solution :D appreciate it though – Gerasimos Ragavanis Sep 21 '21 at 10:14
  • Note: from my experience if the Typed error extends something like a generic `Error` then both of the types will validate as true. If you want to explicitly check that it's the type that extended the other type but not just that type you'll need to utilize a `toStrictEquals` which will require you to go the route of using a `try` `catch` so that you can validate the exception that is thrown using the `toStrictEquals` instead. Hopefully in the future they will have something like `toStrictThrow`. – CTS_AE Nov 09 '22 at 21:48
  • @PeterDanis Your answer was almost perfect but it took some time to fix my issue as I forgot to wrap my function into an anonymous function. The reason was that I first read `expect(function).toThrow()` and somehow missed your part about that I need to wrap it. Therefore, would it be possible to adapt the section to something like `expect( () => { your-function } ).toThrow()`. In addition you could reference the link https://jestjs.io/docs/expect#tothrowerror to your note that it needs to be wrapped in a anonymous function. – ced-mos Mar 23 '23 at 15:45
194

It is a little bit weird, but it works and IMHO is good readable:

it('should throw Error with message \'UNKNOWN ERROR\' when no parameters were passed', () => {
  try {
      throwError();
      // Fail test if above expression doesn't throw anything.
      expect(true).toBe(false);
  } catch (e) {
      expect(e.message).toBe("UNKNOWN ERROR");
  }
});

The Catch block catches your exception, and then you can test on your raised Error. Strange expect(true).toBe(false); is needed to fail your test if the expected Error will be not thrown. Otherwise, this line is never reachable (Error should be raised before them).

@Kenny Body suggested a better solution which improve a code quality if you use expect.assertions():

it('should throw Error with message \'UNKNOWN ERROR\' when no parameters were passed', () => {
  expect.assertions(1);
  try {
      throwError();
  } catch (e) {
      expect(e.message).toBe("UNKNOWN ERROR");
  }
});

See the original answer with more explanations: How to test the type of a thrown exception in Jest

EDIT 2022:

To use this approach and not trigger no-conditional-expect rule (if you're using eslint-plugin-jest), documentation of this rule suggest to use error wrapper:

class NoErrorThrownError extends Error {}

const getError = async <TError>(call: () => unknown): Promise<TError> => {
  try {
    await call();

    throw new NoErrorThrownError();
  } catch (error: unknown) {
    return error as TError;
  }
};

describe('when the http request fails', () => {
  it('includes the status code in the error', async () => {
    const error = await getError(async () => makeRequest(url));

    // check that the returned error wasn't that no error was thrown
    expect(error).not.toBeInstanceOf(NoErrorThrownError);
    expect(error).toHaveProperty('statusCode', 404);
  });
});

See: no-conditional-expect docs

Paweł BB Drozd
  • 4,483
  • 4
  • 21
  • 17
  • 37
    This is a very verbose way of testing for exceptions when Jest already has the expect.toThrow() way of checking for exceptions: http://jestjs.io/docs/en/expect.html#tothrowerror – gomisha Jun 26 '18 at 13:51
  • 23
    Yes, but it tests only type, not message or other content and the question was about test message, not type. – Paweł BB Drozd Aug 09 '18 at 22:27
  • 2
    Hah. Really like this one as my code needs to test a value of the thrown error so I need the instance. I would write the faulty expectation like `expect('here').not.toBe('here');` just for the fun of it :-) – Valery Aug 16 '18 at 12:54
  • 21
    @Valery or: `expect('to be').not.toBe('to be')` in Shakespeare style. – Michiel van der Blonk Oct 28 '18 at 12:15
  • 1
    This is the only method I’ve found that allows me to test additional properties of custom errors. https://github.com/facebook/jest/issues/6868 – Rúnar Berg Apr 16 '19 at 17:18
  • 1
    I also had to do `console.error = jest.fn()` in the beginning of my test to suppress console message about the unhandled error. – AnaPana May 17 '19 at 14:19
  • I like this solution as a nice inspiration to write tests. `expect.toThrow()` is surely the simpler and faster solution, but I felt I learned something reading this answer. – Andru Sep 02 '19 at 11:36
  • This is a good answer but the example given can be achieved with `expect().toThrow()` as it is only checking the error message and not the type. As such, it does not answer the original question. Also, instead of `expect(true).toBe(false);`, you can use `expect.assertions(1)` at the start of your test. See my answer for a full example/explanation - https://stackoverflow.com/a/58103698/3361387 – Kenny Body Sep 25 '19 at 17:34
  • @KennyBody Nice, it looks much better as such a way of tweaking like mine. I'll recommend this. – Paweł BB Drozd Sep 26 '19 at 08:46
  • 12
    You don't need to make a fake assertion to fail - you can simply use `fail(...)` :) – jocull Mar 19 '20 at 08:38
136

I use a slightly more concise version:

expect(() => {
  // Code block that should throw error
}).toThrow(TypeError) // Or .toThrow('expectedErrorMessage')
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Tal Joffe
  • 5,347
  • 4
  • 25
  • 31
94

From my (albeit limited) exposure to Jest, I have found that expect().toThrow() is suitable if you want to only test an error is thrown of a specific type:

expect(() => functionUnderTest()).toThrow(TypeError);

Or an error is thrown with a specific message:

expect(() => functionUnderTest()).toThrow('Something bad happened!');

If you try to do both, you will get a false positive. For example, if your code throws RangeError('Something bad happened!'), this test will pass:

expect(() => functionUnderTest()).toThrow(new TypeError('Something bad happened!'));

The answer by bodolsog which suggests using a try/catch is close, but rather than expecting true to be false to ensure the expect assertions in the catch are hit, you can instead use expect.assertions(2) at the start of your test where 2 is the number of expected assertions. I feel this more accurately describes the intention of the test.

A full example of testing the type and message of an error:

describe('functionUnderTest', () => {
    it('should throw a specific type of error.', () => {
        expect.assertions(2);

        try {
            functionUnderTest();
        } catch (error) {
            expect(error).toBeInstanceOf(TypeError);
            expect(error).toHaveProperty('message', 'Something bad happened!');
        }
    });
});

If functionUnderTest() does not throw an error, the assertions will be be hit, but the expect.assertions(2) will fail and the test will fail.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Kenny Body
  • 1,059
  • 8
  • 5
  • D'oh. I always forget about the expecting multiple assertions feature of Jest (possibly I just don't personally find it the most intutitive, but it definitely works for such cases!) Cheers! – kpollock Sep 26 '19 at 07:27
  • 3
    `expect.hasAssertions()` might a better alternative when the test doesn't have any assertions outside `catch`, because you don't have to update the number if you add/remove assertions. – André Sassi May 04 '20 at 09:44
  • Another way to test the type and message is using [`toThrowWithMessage(type, message)`](https://github.com/jest-community/jest-extended#tothrowwithmessagetype-message) from the jest-extended project. – Mark Doliner Oct 28 '20 at 00:50
  • The solution is otherwise good, but what if functionUnderTest passes and expections under catch block are never tested? I'd recommend moving the expects under main functions so they'd always be tested – Ilmari Kumpula Apr 15 '21 at 08:29
  • 1
    @IlmariKumpula That's why you have the first line which defines how many assertions you are expecting. If the functionUnderTest passes and the catch block is never entered then the expects don't get hit and the test fails because two assertions were not made. – Kenny Body Sep 06 '21 at 15:55
64

I manage to combine some answers and end up with this:

it('should throw', async () => {
    await expect(service.methodName('some@email.com', 'unknown')).rejects.toThrow(
      HttpException,
    );
  });
Douglas Caina
  • 932
  • 10
  • 8
46

Modern Jest allows you to make more checks on a rejected value. For example, you could test status code of http exception:

const request = Promise.reject({statusCode: 404})
await expect(request).rejects.toMatchObject({ statusCode: 500 });

will fail with error

Error: expect(received).rejects.toMatchObject(expected)

- Expected
+ Received

  Object {
-   "statusCode": 500,
+   "statusCode": 404,
  }
Slava Baginov
  • 949
  • 1
  • 8
  • 10
33

Further to Peter Danis' post, I just wanted to emphasize the part of his solution involving "[passing] a function into expect(function).toThrow(blank or type of error)".

In Jest, when you test for a case where an error should be thrown, within your expect() wrapping of the function under testing, you need to provide one additional arrow function wrapping layer in order for it to work. I.e.

Wrong (but most people's logical approach):

expect(functionUnderTesting();).toThrow(ErrorTypeOrErrorMessage);

Right:

expect(() => { functionUnderTesting(); }).toThrow(ErrorTypeOrErrorMessage);

It's very strange, but it should make the testing run successfully.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Adrian
  • 1,683
  • 10
  • 6
  • 5
    Thanks for the answer. And it's incredible how the Jest's documentation can make things difficult by hidden important infos about the functionality. – Pablo Lopes Sep 24 '20 at 18:54
  • Even shorter is `expect(functionUnderTesting).toThrow(ErrorTypeOrErrorMessage)` – Mike Harrison Mar 24 '22 at 18:35
  • Don't know why but even this way it doesn't work for me. When exceptions are thrown the tests running stop just like a fatal exception error instead of catching the error and asserting. Only approach that worked was using the try catch inside the test case – Danilo Marques Sep 29 '22 at 14:58
  • Thanks, I was wondering why it would still complain about the test failing because an exception has been thrown. Wrapping it in a lambda worked like a charm – Metalfreak Jul 14 '23 at 07:57
28

In case you are working with Promises:

await expect(Promise.reject(new HttpException('Error message', 402)))
  .rejects.toThrowError(HttpException);
Željko Šević
  • 3,743
  • 2
  • 26
  • 23
21

Check out toThrow method.

You must wrap the code in an additional function callback!

You should check both: the error message and its type.

For example:

// additional function wrap
const wrap = () => {
  yourCodeToTest();
};

// test error message
expect(wrap).toThrow('UNKNOWN ERROR');

// test error type
expect(wrap).toThrow(TypeError);

Because of additional callback wrap, the code will not be run immediately, so jest will be able to catch it.

You should always check the error message to be sure you are checking the correct throw case and not getting another error your code may throw.

It is also nice to check the error type, so the client code may rely on it.

Igor Sukharev
  • 2,467
  • 24
  • 21
  • 1
    This way only checks the error message if you use `new TypeError("error message")`, better to check twice, one checks error message, and another one checks error type. – Kaiwen Luo Apr 28 '23 at 01:17
19

There's a way to wait an error that comes from a async function, you just have to write your code like in the example bellow

await expect(yourAsyncFunction()).rejects.toThrowError();
Hiran Júnior
  • 345
  • 2
  • 9
17

You must wrap the code of the function that you are expecting in another arrow function, otherwise the error will not be caught and the assertion will fail.

the function you want to test :

const testThrowingError = () => {
    throw new Error();
  };

the test:

describe("error function should Throw Error", () => {
  expect(() =>testThrowingError()).toThrowError();
});

resource: https://jestjs.io/docs/expect#tothrowerror

Safi Habhab
  • 981
  • 10
  • 17
15

I haven't tried it myself, but I would suggest using Jest's toThrow assertion. So I guess your example would look something like this:

it('should throw Error with message \'UNKNOWN ERROR\' when no parameters were passed', (t) => {
  const error = t.throws(() => {
    throwError();
  }, TypeError);

  expect(t).toThrowError('UNKNOWN ERROR');
  //or
  expect(t).toThrowError(TypeError);
});

Again, I haven't test it, but I think it should work.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Andrei CACIO
  • 2,101
  • 15
  • 28
13

I have successfully used this

await expect(
      async () => await apiCalls()
    ).rejects.toThrow();
Liu Hantao
  • 620
  • 1
  • 9
  • 19
12

Jest has a method, toThrow(error), to test that a function throws when it is called.

So, in your case you should call it so:

expect(t).toThrowError(TypeError);

The documentation.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
alexmac
  • 19,087
  • 7
  • 58
  • 69
  • 1
    It wouldn't work for the case: `jest.spyOn(service, 'create').mockImplementation(() => { throw new Error(); });` if the mocked method `create` is not `async`. – Serg Mar 09 '19 at 11:47
6

The documentation is clear on how to do this. Let's say I have a function that takes two parameters and it will throw an error if one of them is null.

function concatStr(str1, str2) {
  const isStr1 = str1 === null
  const isStr2 = str2 === null
  if(isStr1 || isStr2) {
    throw "Parameters can't be null"
  }
  ... // Continue your code

Your test

describe("errors", () => {
  it("should error if any is null", () => {
    // Notice that the expect has a function that returns the function under test
    expect(() => concatStr(null, "test")).toThrow()
  })
})
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
redeemefy
  • 4,521
  • 6
  • 36
  • 51
2

I ended up writing a convenience method for our test-utils library

/**
 *  Utility method to test for a specific error class and message in Jest
 * @param {fn, expectedErrorClass, expectedErrorMessage }
 * @example   failTest({
      fn: () => {
        return new MyObject({
          param: 'stuff'
        })
      },
      expectedErrorClass: MyError,
      expectedErrorMessage: 'stuff not yet implemented'
    })
 */
  failTest: ({ fn, expectedErrorClass, expectedErrorMessage }) => {
    try {
      fn()
      expect(true).toBeFalsy()
    } catch (err) {
      let isExpectedErr = err instanceof expectedErrorClass
      expect(isExpectedErr).toBeTruthy()
      expect(err.message).toBe(expectedErrorMessage)
    }
  }
kpollock
  • 3,899
  • 9
  • 42
  • 61
  • 1
    The same can be done using Jests own features. See my answer for how this can be done - https://stackoverflow.com/a/58103698/3361387 – Kenny Body Sep 25 '19 at 17:31
  • Also the separate jest-extended project has a [`toThrowWithMessage(type, message)`](https://github.com/jest-community/jest-extended#tothrowwithmessagetype-message) matcher that's pretty great. – Mark Doliner Oct 28 '20 at 00:49
1

There is also an easier way to assert against the error message. The beauty of this method is that you don't need to reconstruct the error object or have the full error message. As long as your error contains part of the error message we can assume it is of the correct type. i.e

 const printOnlyString = (str) => {
   if(typeof str !== "string"){
     throw Error("I can only print strings ${typeof str) given");
   }
   else {
     console.log(str);
   } 
 }

expect(() => printOnlyString(123)).toThrow(/can only print strings/)

Yasin Yaqoobi
  • 1,888
  • 3
  • 27
  • 38
1

To test whether a function throws a specific error message with a specific type of error, you can use Jest's toThrow() matcher

function myFunction() {
  throw new TypeError('Something went wrong');
}

describe('myFunction', () => {
  it('should throw a TypeError with a specific error message', () => {
    expect(myFunction).toThrow(TypeError);
    expect(myFunction).toThrow(/Something went wrong/);

    //noted: this way will invoke the myFunction twice
    expect(myFunction).toBeCalledTimes(2)
  });
});

If you use expect(myFunction).toThrow(new TypeError("Something went wrong"), it only checks the error message rather than the error type, which does not meet the test purpose.

Kaiwen Luo
  • 370
  • 4
  • 10
0

A good way is to create custom error classes and mock them. Then you can assert whatever you want.

MessedUpError.ts

type SomeCrazyErrorObject = {
  [key: string]: unknown,
}

class MessedUpError extends Error {
  private customErrorData: SomeCrazyErrorObject = {};

  constructor(err?: string, data?: SomeCrazyErrorObject) {
    super(err || 'You messed up');

    Object.entries(data ?? {}).forEach(([Key, value]) => {
      this.customErrorData[Key] = value;
    });
    Error.captureStackTrace(this, this.constructor);
  }

  logMe() {
    console.log(this.customErrorData);
  }
}

export default MessedUpError;

messedUpError.test.ts

import MessedUpError from './MessedUpError';

jest.mock('./MessedUpError', () => jest.fn().mockImplementation((...args: any[]) => ({
  constructor: args,
  log: () => {},
})));

type MessedUpErrorContructorParams = Expand<typeof MessedUpError['prototype']>
const MessedUpErrorMock = MessedUpError as unknown as jest.Mock<MessedUpError, [MessedUpErrorContructorParams]>;

const serverErrorContructorCall = (i = 0) => ({
  message: MessedUpErrorMock.mock.calls[i][0],
  ...MessedUpErrorMock.mock.calls[i][1] || {},
});

beforeEach(() => {
  MessedUpErrorMock.mockClear();
});

test('Should throw', async () => {
  try {
    await someFunctionThatShouldThrowMessedUpError();
  } catch {} finally {
    expect(MessedUpErrorMock).toHaveBeenCalledTimes(1);
    const constructorParams = serverErrorContructorCall();
    expect(constructorParams).toHaveProperty('message', 'You messed up');
    expect(constructorParams).toHaveProperty('customErrorProperty', 'someValue');
  }
});

The assertions always go inside the finally clause. This way it will always be asserted. Even if the test does not throw any errors.

Omar Omeiri
  • 1,506
  • 1
  • 17
  • 33
0

If you want something less verbose in your tests but want to test an Error is of a class and has a certain message (or status, or any other properties), you can write a custom matcher.

We use something pretty simple, but you can extend this to include RegExp, etc.

// In jest.setup.js
expect.extend({
  toThrowErrorWithMessage: (received, errorType, message) => {
    if (!received) {
      return {
        message: () =>
          `expected error to be of type ${errorType.name} with message matching ${message}, received ${received}`,
        pass: false,
      };
    }

    if (!(received instanceof errorType)) {
      return {
        message: () => `expected error of type ${errorType} received ${received}`,
        pass: false,
      };
    }

    const validMessage = received.message && received.message.match(message);

    if (!validMessage) {
      return {
        message: () =>
          `expected error message to match "${message}", received "${received.message}"`,
        pass: false,
      };
    }

    return {
      pass: true,
      // Note: this message is for when using .not
      message: () =>
        `expected error not to be of type ${errorType.name} with message matching "${message}"`,
    };
  },
});

// In a test
expect(() => validate()).rejects.toThrowErrorWithMessage(BadRequestError, 'Oh noes')
Joel Tadmor
  • 121
  • 5
0

Similar to the try/catch, if you are looking to catch an error for an async function one can also just append a .catch() to the function to be able to access error value: await asyncFunc().catch(e => { expect(e.toString()).toBe("Error: My error message") }) or whatever functionality you are looking to test

super IT guy
  • 195
  • 2
  • 12
0

Jest version: 29
You have an async function that needs to be tested for throwing an error.

await expect(listener.onMessage(data, msg)).rejects.toThrow();

Assert with exact error message:

await expect(listener.onMessage(data, msg)).rejects.toThrow("Data not found!");
Ikram Ud Daula
  • 1,213
  • 14
  • 21
-2

Try:

expect(t).rejects.toThrow()
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • 7
    Why `try`? there is not try - but answer. If this is answer please elaborate more. what you adding to the existing answer? – dWinder May 16 '19 at 17:38
  • 12
    I think @Razim was saying that you should try the solution, not use a try catch. – Tom Aug 20 '19 at 23:05