我正在使用一些代码,其中我需要测试由函数抛出的异常的类型(它是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重写我的测试,但不知道如何轻松地做到这一点。这可能吗?
开玩笑地说,你必须将一个函数传递给expect(函数)。toThrow(<空白或错误类型>)。
例子:
test("Test description", () => {
const t = () => {
throw new TypeError();
};
expect(t).toThrow(TypeError);
});
或者如果你还想检查错误信息:
test("Test description", () => {
const t = () => {
throw new TypeError("UNKNOWN ERROR");
};
expect(t).toThrow(TypeError);
expect(t).toThrow("UNKNOWN ERROR");
});
如果需要测试现有函数是否抛出一组参数,则必须将其包装在expect()中的匿名函数中。
例子:
test("Test description", () => {
expect(() => {http.get(yourUrl, yourCallbackFn)}).toThrow(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)
}
}
从我对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)将失败,测试也将失败。
文档清楚地说明了如何做到这一点。假设我有一个函数,它有两个参数,如果其中一个为空,它就会抛出一个错误。
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()
})
})
一个好方法是创建自定义错误类并模拟它们。然后你可以断言任何你想要的东西。
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子句中。通过这种方式,它将始终被断言。即使测试没有抛出任何错误。
您必须将期望的函数代码包装在另一个箭头函数中,否则错误将不会被捕获,断言将失败。
要测试的函数:
const testThrowingError = () => {
throw new Error();
};
测试:
describe("error function should Throw Error", () => {
expect(() =>testThrowingError()).toThrowError();
});
资源:
https://jestjs.io/docs/expect#tothrowerror