如何测试Jest中抛出的异常类型

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

485038 次浏览

我自己没有尝试过,但我建议使用Jest的toThrow断言。所以我猜你的例子应该是这样的:

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

同样,我还没有测试它,但我认为它应该可以工作。

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

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

expect(t).toThrowError(TypeError);

的文档

开玩笑地说,你必须将一个函数传递给expect(function).toThrow(<blank or type of error>)

例子:

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上进行测试。如果没有抛出预期的Error,则需要奇怪的expect(true).toBe(false);来失败测试。否则,这一行永远无法到达(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文档

试一试:

expect(t).rejects.toThrow()

我用了一个稍微简洁一点的版本:

expect(() => {
// Code block that should throw error
}).toThrow(TypeError) // Or .toThrow('expectedErrorMessage')

我最终为我们的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('Something bad happened!');

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

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

bodolsog给出了答案建议使用try/catch接近,但与其期望true为false以确保捕获中的预期断言被命中,您可以在测试开始时使用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)将失败,测试将失败。

Modern Jest允许您对被拒绝的值进行更多检查。例如,你可以测试http异常的状态代码:

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

会出错

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


- Expected
+ Received


Object {
-   "statusCode": 500,
+   "statusCode": 404,
}

的文档清楚地说明了如何做到这一点。假设我有一个函数,它有两个参数,如果其中一个是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

您的测试

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

如果你正在使用Promises:

await expect(Promise.reject(new HttpException('Error message', 402)))
.rejects.toThrowError(HttpException);

除了彼得·达尼斯的帖子之外,我只是想强调他的解决方案中涉及“[传递]一个函数到expect(函数)”的部分。toThrow(空白或错误类型)"

在Jest中,当您测试应该抛出错误的情况时,在测试函数的expect()包装中,您需要提供一个额外的箭头函数包装层以使其工作。即。

错误的(但大多数人的逻辑方法):

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

正确的:

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

这很奇怪,但它应该能使测试成功运行。

检查toThrow方法。

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

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

例如:

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

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

你应该总是检查错误信息,以确保你检查的是正确的throw情况,而不是得到另一个错误,你的代码可能throw

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

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

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

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

await expect(
async () => await apiCalls()
).rejects.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();
});
< p >资源: https://jestjs.io/docs/expect#tothrowerror < / p >

有一种方法可以等待来自异步函数的错误,你只需要像下面的例子那样编写代码

await expect(yourAsyncFunction()).rejects.toThrowError();

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

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