hth*_*tho 2 settimeout jsdom requestanimationframe jestjs
我想为使用requestAnimationFrame
和的模块编写一个开玩笑的单元测试cancelAnimationFrame
。
我尝试用我自己的模拟覆盖 window.requestAnimationFrame(如本答案中所建议的那样),但该模块继续使用 jsdom 提供的实现。
我目前的方法是使用requestAnimationFrame
来自 jsdom的(以某种方式)内置实现,它似乎在幕后使用setTimeout
,应该可以通过使用jest.useFakeTimers()
.
jest.useFakeTimers();
describe("fakeTimers", () => {
test.only("setTimeout and trigger", () => {
const order: number[] = [];
expect(order).toEqual([]);
setTimeout(t => order.push(1));
expect(order).toEqual([]);
jest.runAllTimers();
expect(order).toEqual([1]);
});
test.only("requestAnimationFrame and runAllTimers", () => {
const order: number[] = [];
expect(order).toEqual([]);
requestAnimationFrame(t => order.push(1));
expect(order).toEqual([]);
jest.runAllTimers();
expect(order).toEqual([1]);
});
});
Run Code Online (Sandbox Code Playgroud)
第一次测试成功,第二次失败,因为order
是空的。
测试依赖于requestAnimationFrame()
. 特别是如果我需要测试帧被取消的条件?
我不确定这个解决方案是否完美,但这适用于我的情况。
\n\n这里有两个关键原则。
\n\n1) 创建基于 requestAnimationFrame 的延迟:
\n\nconst waitRAF = () => new Promise(resolve => requestAnimationFrame(resolve));\n
Run Code Online (Sandbox Code Playgroud)\n\n2)使我正在测试的动画运行得非常快:
\n\n就我而言,我正在等待的动画有一个可配置的持续时间,在我的 props 数据中设置为 1。
\n\n另一个解决方案可能是多次运行 waitRaf 方法,但这会减慢测试速度。
\n\n您可能还需要模拟 requestAnimationFrame 但这取决于您的设置、测试框架和实现
\n\n我的示例测试文件(带有 Jest 的 Vue 应用程序):
\n\nimport { mount } from \'@vue/test-utils\';\nimport AnimatedCount from \'@/components/AnimatedCount.vue\';\n\nconst waitRAF = () => new Promise(resolve => requestAnimationFrame(resolve));\n\nlet wrapper;\ndescribe(\'AnimatedCount.vue\', () => {\n beforeEach(() => {\n wrapper = mount(AnimatedCount, {\n propsData: {\n value: 9,\n duration: 1,\n formatDisplayFn: (val) => "\xc2\xa3" + val\n }\n });\n });\n\n it(\'renders a vue instance\', () => {\n expect(wrapper.isVueInstance()).toBe(true);\n });\n\n describe(\'When a value is passed in\', () => {\n it(\'should render the correct amount\', async () => {\n const valueOutputElement = wrapper.get("span");\n wrapper.setProps({ value: 10 });\n\n await wrapper.vm.$nextTick();\n await waitRAF();\n\n expect(valueOutputElement.text()).toBe("\xc2\xa310");\n })\n })\n});\n
Run Code Online (Sandbox Code Playgroud)\n
所以,我自己找到了解决方案。
我真的需要重写window.requestAnimationFrame
和window.cancelAnimationFrame
。
问题是,我没有正确包含模拟模块。
// mock_requestAnimationFrame.js
class RequestAnimationFrameMockSession {
handleCounter = 0;
queue = new Map();
requestAnimationFrame(callback) {
const handle = this.handleCounter++;
this.queue.set(handle, callback);
return handle;
}
cancelAnimationFrame(handle) {
this.queue.delete(handle);
}
triggerNextAnimationFrame(time=performance.now()) {
const nextEntry = this.queue.entries().next().value;
if(nextEntry === undefined) return;
const [nextHandle, nextCallback] = nextEntry;
nextCallback(time);
this.queue.delete(nextHandle);
}
triggerAllAnimationFrames(time=performance.now()) {
while(this.queue.size > 0) this.triggerNextAnimationFrame(time);
}
reset() {
this.queue.clear();
this.handleCounter = 0;
}
};
export const requestAnimationFrameMock = new RequestAnimationFrameMockSession();
window.requestAnimationFrame = requestAnimationFrameMock.requestAnimationFrame.bind(requestAnimationFrameMock);
window.cancelAnimationFrame = requestAnimationFrameMock.cancelAnimationFrame.bind(requestAnimationFrameMock);
Run Code Online (Sandbox Code Playgroud)
必须在导入任何可能调用requestAnimationFrame
.
// mock_requestAnimationFrame.test.js
import { requestAnimationFrameMock } from "./mock_requestAnimationFrame";
describe("mock_requestAnimationFrame", () => {
beforeEach(() => {
requestAnimationFrameMock.reset();
})
test("reqest -> trigger", () => {
const order = [];
expect(requestAnimationFrameMock.queue.size).toBe(0);
expect(order).toEqual([]);
requestAnimationFrame(t => order.push(1));
expect(requestAnimationFrameMock.queue.size).toBe(1);
expect(order).toEqual([]);
requestAnimationFrameMock.triggerNextAnimationFrame();
expect(requestAnimationFrameMock.queue.size).toBe(0);
expect(order).toEqual([1]);
});
test("reqest -> request -> trigger -> trigger", () => {
const order = [];
expect(requestAnimationFrameMock.queue.size).toBe(0);
expect(order).toEqual([]);
requestAnimationFrame(t => order.push(1));
requestAnimationFrame(t => order.push(2));
expect(requestAnimationFrameMock.queue.size).toBe(2);
expect(order).toEqual([]);
requestAnimationFrameMock.triggerNextAnimationFrame();
expect(requestAnimationFrameMock.queue.size).toBe(1);
expect(order).toEqual([1]);
requestAnimationFrameMock.triggerNextAnimationFrame();
expect(requestAnimationFrameMock.queue.size).toBe(0);
expect(order).toEqual([1, 2]);
});
test("reqest -> cancel", () => {
const order = [];
expect(requestAnimationFrameMock.queue.size).toBe(0);
expect(order).toEqual([]);
const handle = requestAnimationFrame(t => order.push(1));
expect(requestAnimationFrameMock.queue.size).toBe(1);
expect(order).toEqual([]);
cancelAnimationFrame(handle);
expect(requestAnimationFrameMock.queue.size).toBe(0);
expect(order).toEqual([]);
});
test("reqest -> request -> cancel(1) -> trigger", () => {
const order = [];
expect(requestAnimationFrameMock.queue.size).toBe(0);
expect(order).toEqual([]);
const handle = requestAnimationFrame(t => order.push(1));
requestAnimationFrame(t => order.push(2));
expect(requestAnimationFrameMock.queue.size).toBe(2);
expect(order).toEqual([]);
cancelAnimationFrame(handle);
expect(requestAnimationFrameMock.queue.size).toBe(1);
expect(order).toEqual([]);
requestAnimationFrameMock.triggerNextAnimationFrame();
expect(requestAnimationFrameMock.queue.size).toBe(0);
expect(order).toEqual([2]);
});
test("reqest -> request -> cancel(2) -> trigger", () => {
const order = [];
expect(requestAnimationFrameMock.queue.size).toBe(0);
expect(order).toEqual([]);
requestAnimationFrame(t => order.push(1));
const handle = requestAnimationFrame(t => order.push(2));
expect(requestAnimationFrameMock.queue.size).toBe(2);
expect(order).toEqual([]);
cancelAnimationFrame(handle);
expect(requestAnimationFrameMock.queue.size).toBe(1);
expect(order).toEqual([]);
requestAnimationFrameMock.triggerNextAnimationFrame();
expect(requestAnimationFrameMock.queue.size).toBe(0);
expect(order).toEqual([1]);
});
test("triggerAllAnimationFrames", () => {
const order = [];
expect(requestAnimationFrameMock.queue.size).toBe(0);
expect(order).toEqual([]);
requestAnimationFrame(t => order.push(1));
requestAnimationFrame(t => order.push(2));
requestAnimationFrameMock.triggerAllAnimationFrames();
expect(order).toEqual([1,2]);
});
test("does not fail if triggerNextAnimationFrame() is called with an empty queue.", () => {
requestAnimationFrameMock.triggerNextAnimationFrame();
})
});
Run Code Online (Sandbox Code Playgroud)
这里是开玩笑问题的解决方案:
beforeEach(() => {
jest.spyOn(window, 'requestAnimationFrame').mockImplementation(cb => cb());
});
afterEach(() => {
window.requestAnimationFrame.mockRestore();
});
Run Code Online (Sandbox Code Playgroud)