Je veux écrire un test unitaire de plaisanterie pour un module qui utilise requestAnimationFrame
et cancelAnimationFrame
.
J'ai essayé de remplacer window.requestAnimationFrame avec ma propre maquette (comme suggéré dans cette réponse ), mais le module continue d'utiliser l'implémentation fournie par jsdom .
Mon approche actuelle consiste à utiliser l'implémentation (en quelque sorte) intégrée de requestAnimationFrame
de jsdom, qui semble utiliser setTimeout
sous le capot, ce qui devrait être mockable en utilisant 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]); }); });
Le premier test est réussi, tandis que le second échoue, car order code > est vide.
Quelle est la bonne façon de tester le code qui repose sur requestAnimationFrame ()
. Surtout si j'ai besoin de tester les conditions où une image a été annulée?
3 Réponses :
Je ne suis pas sûr que cette solution soit parfaite, mais cela fonctionne pour mon cas.
Deux principes clés fonctionnent ici.
1) Créez un délai basé sur requestAnimationFrame :
import { mount } from '@vue/test-utils'; import AnimatedCount from '@/components/AnimatedCount.vue'; const waitRAF = () => new Promise(resolve => requestAnimationFrame(resolve)); let wrapper; describe('AnimatedCount.vue', () => { beforeEach(() => { wrapper = mount(AnimatedCount, { propsData: { value: 9, duration: 1, formatDisplayFn: (val) => "£" + val } }); }); it('renders a vue instance', () => { expect(wrapper.isVueInstance()).toBe(true); }); describe('When a value is passed in', () => { it('should render the correct amount', async () => { const valueOutputElement = wrapper.get("span"); wrapper.setProps({ value: 10 }); await wrapper.vm.$nextTick(); await waitRAF(); expect(valueOutputElement.text()).toBe("£10"); }) }) });
2) Rendre l'animation que je teste très rapide:
Dans mon cas, le L'animation que j'attendais a une durée configurable qui est définie sur 1 dans mes données d'accessoires.
Une autre solution à cela pourrait potentiellement être d'exécuter la méthode waitRaf plusieurs fois, mais cela ralentira les tests.
Vous devrez peut-être également simuler requestAnimationFrame, mais cela dépend de votre configuration, de votre cadre de test et de votre implémentation
Mon exemple de fichier de test (application Vue avec Jest):
const waitRAF = () => new Promise(resolve => requestAnimationFrame(resolve));
J'ai donc trouvé la solution moi-même.
J'avais vraiment besoin de remplacer window.requestAnimationFrame
et window.cancelAnimationFrame
.
Le problème était, que je n'ai pas inclus correctement le module de simulation.
// 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(); }) });
La maquette doit être importée AVANT que tout module soit importé qui pourrait appeler requestAnimationFrame
.
// 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);
Voici la solution du problème :
beforeEach(() => { jest.spyOn(window, 'requestAnimationFrame').mockImplementation(cb => cb()); }); afterEach(() => { window.requestAnimationFrame.mockRestore(); });