我正在使用一些代码,其中我需要测试由函数抛出的异常的类型(它是TypeError, ReferenceError等?)

我目前的测试框架是AVA,我可以测试它作为第二个参数t.throws方法,就像这里:

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

我开始用Jest重写我的测试,但不知道如何轻松地做到这一点。这可能吗?


当前回答

从我对Jest的接触(尽管有限)中,我发现expect().toThrow()适用于只想测试抛出的特定类型的错误:

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

或者抛出一个带有特定消息的错误:

expect(() => functionUnderTest())。toThrow('发生了不好的事情!');

如果你试图同时做这两件事,你会得到一个假阳性。例如,如果你的代码抛出RangeError('坏事发生了!'),这个测试将通过:

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

bodolsog给出的答案很接近,它建议使用try/catch,但与其期望true为false以确保捕获中的expect断言被命中,不如在测试开始时使用expect.assertions(2),其中2是预期断言的数量。我觉得这更准确地描述了测试的意图。

测试错误类型和消息的完整示例:

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

如果functionUnderTest()没有抛出错误,则断言将被命中,但expect.assertions(2)将失败,测试也将失败。

其他回答

从我对Jest的接触(尽管有限)中,我发现expect().toThrow()适用于只想测试抛出的特定类型的错误:

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

或者抛出一个带有特定消息的错误:

expect(() => functionUnderTest())。toThrow('发生了不好的事情!');

如果你试图同时做这两件事,你会得到一个假阳性。例如,如果你的代码抛出RangeError('坏事发生了!'),这个测试将通过:

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

bodolsog给出的答案很接近,它建议使用try/catch,但与其期望true为false以确保捕获中的expect断言被命中,不如在测试开始时使用expect.assertions(2),其中2是预期断言的数量。我觉得这更准确地描述了测试的意图。

测试错误类型和消息的完整示例:

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

如果functionUnderTest()没有抛出错误,则断言将被命中,但expect.assertions(2)将失败,测试也将失败。

我设法把一些答案结合起来,最后得到了这样的答案:

it('should throw', async () => {
    await expect(service.methodName('some@email.com', 'unknown')).rejects.toThrow(
      HttpException,
    );
  });

Jest有一个方法toThrow(error),用于测试函数在被调用时是否抛出。

所以,在你的情况下,你应该这样称呼它:

expect(t).toThrowError(TypeError);

文档。

这有点奇怪,但它是有效的,以我之见是很好的可读性:

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

Catch块捕获您的异常,然后您可以测试您引发的Error。奇怪的期待(真正的).toBe(假);如果没有抛出预期的错误,则需要测试失败。否则,这一行永远无法到达(Error应该在它们之前引发)。

@Kenny Body提出了一个更好的解决方案,如果你使用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");
  }
});

请参阅原始答案和更多解释:如何在Jest中测试抛出异常的类型

2022年编辑:

要使用这种方法而不触发no-conditional-expect规则(如果你使用eslint-plugin-jest),该规则的文档建议使用错误包装器:

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

参见:no-conditional-expect文档

我最终为我们的test-utils库编写了一个方便的方法

/**
 *  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)
    }
  }