(十一)Jest 中的 mock timer
平时开发中我们经常用到定时器setInterval 或者setTimeout ,现在我们就写一个定时器的测试用例代码如下:
// demo.js export const lazy = (fn)=> { setTimeout(() => { fn(); }, 3000); } // demo.test.js
import {lazy} from './timerDemo'
test('should call fn after 3s', () => {
const callback = jest.fn();
lazy(callback);
setTimeout(() => {
expect(callback).toBeCalled();
}, 3001);
})
使用npm run test运行测试用例,运行结果如下:
如何解决这个问题呢?
方法一:
之前的笔记有一个done回调函数(五)Jest测试异步代码
此时我们利用done()来解决这个问题,代码如下:
import {lazy} from './timerDemo' test('should call fn after 3s', (done) => { const callback = jest.fn(); lazy(callback); setTimeout(() => { expect(callback).toBeCalled(); done(); }, 3001); })
方法二:
今天我们学习一种新的解决办法,使用mock timer解决这个问题。jest 提供了mock timer 的功能,不要再使用真实的时间在这里等了,一个假的时间模拟一下就可以了。首先是jest.useFakeTimers() 的调用,它就告诉jest 在以后的测试中,可以使用假时间。当然只用它还不行,因为它只是表示可以使用,我们还要告诉jest在哪个地方使用,当jest 在测试的时候,到这个地方,它就自动使用假时间。两个函数,jest.runAllTimers(), 它表示把所有时间都跑完。具体到我们这个测试,我们希望执完lazy(callback) 就调用, 把lazy函数中的3s时间立刻跑完。可以使用jest.runAllTimers();
具体代码如下:
import {lazy} from './timerDemo' jest.useFakeTimers();//可以使用假函数 test('should call fn after 3s', () => { const callback = jest.fn(); lazy(callback); jest.runAllTimers();//让所有定时器立即执行 expect(callback).toBeCalled();
//expect(callback).toHaveBeenCalledTimes(1) 也可以使用
})
如果有多个定时器的时候,我们修改demo.js的代码如下:
export const lazy = (fn)=> { setTimeout(() => { fn(); console.log('第一个定时器执行') setTimeout(()=>{ console.log('第二个定时器执行') },3000) }, 3000); }
此时运行测试用例的时候,两个定时器会立马都被执行掉。但如果我们只想运行最外层的那个定时器时,我们需要引入 runOnlyPendingTimers() ,只执行一个定时操作。具体代码如下:
//demo.js export const lazy = (fn)=> { setTimeout(() => { fn(); console.log('第一个定时器执行') setTimeout(()=>{ console.log('第二个定时器执行') },3000) }, 3000); } //demo.test.js import {lazy} from './timerDemo' jest.useFakeTimers();//可以使用假函数 test('should call fn after 3s', () => { const callback = jest.fn(); lazy(callback); jest.runOnlyPendingTimers();//让所有定时器立即执行 //expect(callback).toBeCalled(); expect(callback).toHaveBeenCalledTimes(1) })
自己试着运行一下我们会发现,只有第一个执行了。
同时也可以使用 jest.advanceTimer() 快进几秒。
具体代码使用如下:
//demo.js export const lazy = (fn)=> { setTimeout(() => { fn(); console.log('第一个定时器执行') setTimeout(()=>{ console.log('第二个定时器执行') },3000) }, 3000); } //demo.test.js import {lazy} from './timerDemo' jest.useFakeTimers();//可以使用假函数 test('should call fn after 3s', () => { const callback = jest.fn(); lazy(callback); jest.advanceTimersByTime(3000) expect(callback).toHaveBeenCalledTimes(1) })
运行结果发现只执行了第一个定时器,如果还想执行第二个,我们可以修改demo.test.js代码如下:
import {lazy} from './timerDemo' jest.useFakeTimers();//可以使用假函数 test('should call fn after 3s', () => { const callback = jest.fn(); lazy(callback); jest.advanceTimersByTime(3000) jest.advanceTimersByTime(3000)//第二个的时间以第一个的时间为基数 //expect(callback).toBeCalled(); expect(callback).toHaveBeenCalledTimes(1) })
此时两个定时器都会执行。 使用jest.advanceTimersByTime(n)快进n时间执行定时,多个advanceTimerByTime连用时,后一个会以前一个的时间为基点,如果不想互相影响,我们可以使用钩子函数beforeEach解决这个问题。
前端交流群:扫码进去