11

I use nestjs (6.5.0) and jest (24.8) and have a method that throws an error:

  public async doSomething(): Promise<{ data: string, error?: string }> {
    throw new BadRequestException({ data: '', error: 'foo' });
  }

How can I write a unit test that checks that we get the expected exception with the expected data? The obvious solution is:

it('test', async () => {
  expect(await userController.doSomething())
    .rejects.toThrowError(new BadRequestException({ data: '', error: 'foo'});
});

but that doesn't work because new BadRequestException() creates an object with a different call stack. How can I test this?

decocijo
  • 908
  • 7
  • 19

3 Answers3

16

Compared to examples in jest documentation, you may have 2 problems here.

  • await should be outside the expect argument
  • rejects implies an error was thrown, so you test for equality

Something like:

it('test', async () => {
  await expect(userController.doSomething())
    .rejects.toEqual(new BadRequestException({ data: '', error: 'foo'});
});
MaxenceS
  • 25
  • 7
Sergio Mazzoleni
  • 1,458
  • 15
  • 24
  • ah, yes, you're right that `await` should be outside and I should test for equality with `rejects`. However, that doesn't solve the main problem: `new BadRequestException` creates a new object with a different call stack than the one in `doSomething`. – decocijo May 20 '19 at 14:47
  • 1
    If you want to test the exact error message, then you should use `message` instead of `error` for BadRequestException object. – Ping Zhao Apr 10 '23 at 16:24
1

Answering my own question:

With a custom matcher (see below) the test can be written as:

it('test', async () => {
  await expect(userController.doSomething()).rejects.toContainException(
    new BadRequestException({ data: '', error: 'foo' }),
  );
});

Custom matcher:

import { HttpException } from '@nestjs/common';

// ensure this is parsed as a module.
export {};

// https://stackoverflow.com/questions/43667085/extending-third-party-module-that-is-globally-exposed

declare global {
  namespace jest {
    interface Matchers<R> {
      toContainException: (expected: R | any) => {};
    }
  }
}

// this will extend the expect with a custom matcher
expect.extend({
  toContainException<T extends HttpException>(received: T, expected: T) {
    const success =
      this.equals(received.message, expected.message) &&
      this.equals(received.getStatus(), expected.getStatus());

    const not = success ? ' not' : '';
    return {
      message: () =>
        `expected Exception ${received.name}${not} to be ${expected.name}` +
        '\n\n' +
        `Expected: ${this.utils.printExpected(expected.message)}, ` +
        `status: ${this.utils.printExpected(expected.getStatus())} \n` +
        `Received: ${this.utils.printReceived(received.message)}, ` +
        `status: ${this.utils.printReceived(received.getStatus())}`,
      pass: success,
    };
  },
});
decocijo
  • 908
  • 7
  • 19
  • Why did this answer got downvoted? Why is it not useful? At least it works. There might be a better solution, but I don't see any other answers or comments that would show them. – decocijo Feb 10 '22 at 18:26
0

Here is a super simple solution that worked for me:

    await expect(something).resolves.toThrowError(
      new Error('No message for signing.'),
    );