我正在尝试为一个简单的轮询功能编写一个测试,该测试将检查API端点,直到获得200 OK
响应并重试任何400
或500
响应,直到maxAttempts。我在单元测试中遇到了麻烦,因为无论模拟响应如何,.then()
和.catch()
似乎从未执行过。
我要测试的功能。
const waitForSsl = ({
onSuccess,
onFailure,
interval = 3,
maxAttempts = 10,
}) => {
const pingInterval = interval * 1000; // in seconds
let attempts = 0;
// TODO Add CertController to Laravel API to support this call.
const ping = () => Axios.get('/status')
.then((res) => { return onSuccess(); })
.catch(() => {
if (attempts < maxAttempts) {
attempts += 1;
setTimeout(ping, pingInterval);
} else {
onFailure();
}
});
// Give server a chance to restart by waiting 5 seconds before starting pings.
setTimeout(ping, 5000);
};
我可以验证该功能是否完全符合我的期望,但我希望自己进行省心的单元测试。
这是我第一次使用玩笑和罪恶之物
it('Should only poll maxAttempts + 1 times', () => {
jest.useFakeTimers();
const onSuccessCallback = () => 'success!';
const onFailureCallback = () => 'failed';
const getStub = sinon.stub(Axios, 'get');
getStub.rejects();
ssl.waitForSsl({
onSuccess: onSuccessCallback,
onFailure: onFailureCallback,
maxAttempts: 1,
});
expect(setTimeout).toHaveBeenCalledTimes(2);
});
此测试失败,并显示错误Expected mock function to have been called two times, but it was called one time
我确实找到了这个post,但是该项目尚未使用异步/等待(ES8),仅在不等待的情况下调用Promise.resolve()不能解决问题。
我愿意使用moxios或jest.mock()
,但我感到当在setTimeout
中用作回呼时,无法测试已解决/已拒绝的诺言。一个工作单元测试以及对模拟工作原理的解释将是一个理想的答案。
答案 0 :(得分:2)
这是一个很好的问题,因为它引起了人们对JavaScript某些独特特性及其幕后工作方式的关注。有关使用Timer Mocks时测试async
代码的完整细分,请参见我的答案here。
对于这个问题,重要的是要注意Timer Mocks replace functions like setTimeout
with mocks要记住它们的名字。然后,在调用jest.advanceTimersByTime
(或{{1}表示jest.runTimersToTime
<22.0.0)时,Jest
将运行在经过的时间内运行的所有内容。
请注意,Jest
通常会为JavaScript消息队列安排一条消息,但是Timer Mocks会进行更改,以使所有内容都在当前正在执行的消息中运行 。
另一方面,当setTimeout
解析或拒绝时,回调将安排在Promise Jobs队列中,该队列在当前消息完成之后,下一条消息开始之前运行 。
因此,任何Promise
回调都有机会运行之前,任何当前正在运行的同步代码都将完成。
因此,在这种情况下,您需要调用Promise
(或jest.advanceTimersByTime
<22.0.0的jest.runTimersToTime
)来运行按{{1} }。
棘手的部分是Jest
函数在Promise Jobs队列中排队一个回调,直到当前同步消息完成后该回调才运行。
因此,您需要中断当前的同步消息,以允许Promise Jobs队列中的回调运行。通过使测试功能ping
并在已解析的setTimeout
上调用ping
,最容易做到这一点,这实际上将其余测试排队在Promise Jobs队列的末尾,从而允许所有操作首先运行。
因此,要把它们放在一起,您的测试将需要交替增加时间并允许async
回调像这样运行:
await