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