I want to write a jest unit test for a module that uses requestAnimationFrame and cancelAnimationFrame.
I tried overriding window.requestAnimationFrame with my own mock (as suggested in this answer), but the module keeps on using the implementation provided by jsdom.
My current approach is to use the (somehow) builtin requestAnimationFrame implementation from jsdom, which seems to use setTimeout under the hood, which should be mockable by using 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]);
});
});
The first test is successful, while the second fails, because order is empty.
What is the correct way to test code that relies on requestAnimationFrame(). Especially if I need to test conditions where a frame was cancelled?
Here solution from the jest issue:
beforeEach(() => {
jest.spyOn(window, 'requestAnimationFrame').mockImplementation(cb => cb());
});
afterEach(() => {
window.requestAnimationFrame.mockRestore();
});
I'm not sure this solution is perfect but this works for my case.
There are two key principles working here.
1) Create a delay that is based on requestAnimationFrame:
const waitRAF = () => new Promise(resolve => requestAnimationFrame(resolve));
2) Make the animation I am testing run very fast:
In my case the animation I was waiting on has a configurable duration which is set to 1 in my props data.
Another solution to this could potentially be running the waitRaf method multiple times but this will slow down tests.
You may also need to mock requestAnimationFrame but that is dependant on your setup, testing framework and implementation
My example test file (Vue app with Jest):
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");
})
})
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With