我正在使用一些代码,其中我需要测试由函数抛出的异常的类型(它是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重写我的测试,但不知道如何轻松地做到这一点。这可能吗?


当前回答

一个好方法是创建自定义错误类并模拟它们。然后你可以断言任何你想要的东西。

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

断言总是在finally子句中。通过这种方式,它将始终被断言。即使测试没有抛出任何错误。

其他回答

文档清楚地说明了如何做到这一点。假设我有一个函数,它有两个参数,如果其中一个为空,它就会抛出一个错误。

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

您的测试

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

检查toThrow方法。

您必须将代码包装在一个额外的回调函数中!

您应该同时检查错误消息及其类型。

例如:

expect(
  () => { // additional function wrap
    yourCodeToTest();
  }
).toThrow(
  new RangeError('duplicate prevArray value: A')
);

由于额外的回调换行,代码不会立即运行,因此jest将能够捕获它。

您应该始终检查错误消息,以确保您正在检查正确的抛出情况,而不会得到代码可能抛出的另一个错误。

检查错误类型也很好,因此客户端代码可以依赖它。

还有一种更简单的方法对错误消息进行断言。这种方法的美妙之处在于,您不需要重新构造错误对象或获得完整的错误消息。只要您的错误包含错误消息的一部分,我们就可以假设它的类型是正确的。即

 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(/只能打印字符串/)

一个好方法是创建自定义错误类并模拟它们。然后你可以断言任何你想要的东西。

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

断言总是在finally子句中。通过这种方式,它将始终被断言。即使测试没有抛出任何错误。

我已经成功地使用了这个方法

await expect(
      async () => await apiCalls()
    ).rejects.toThrow();