Jest: requestAnimationFrame does not invoke callback (v22, jsdom: latest version)

Created on 20 Dec 2017  路  16Comments  路  Source: facebook/jest

Do you want to request a _feature_ or report a _bug_?
A bug

What is the current behavior?
The requestAnimationFrame doesn't invoke callback. Maybe it happened because the jsdom is using setTimeout under the hood, but useFakeTimers and runAllTimers not helping.

If the current behavior is a bug, please provide the steps to reproduce and
either a repl.it demo through https://repl.it/languages/jest or a minimal
repository on GitHub that we can yarn install and yarn test.

  1. Use requestAnimationFrame on your code
  2. try to test what is happened inside callback

What is the expected behavior?
requestAnimationFrame should invoke its callback

Please provide your exact Jest configuration and mention your Jest, node,
yarn/npm version and operating system.

OS: High Sierra
node: 8.9.1
jest: 22.0.3

Needs More Info

Most helpful comment

You can mock requestAnimationFrame with your own implementation.

Here is a workaround:

beforeEach(() => {
  jest.spyOn(window, 'requestAnimationFrame').mockImplementation(cb => cb());
});

afterEach(() => {
  window.requestAnimationFrame.mockRestore();
});

All 16 comments

You can mock requestAnimationFrame with your own implementation.

Here is a workaround:

beforeEach(() => {
  jest.spyOn(window, 'requestAnimationFrame').mockImplementation(cb => cb());
});

afterEach(() => {
  window.requestAnimationFrame.mockRestore();
});

@lukomwro Great solution, you're right, big thanks

I cannot reproduce this, raf works for me. Can you provide a reproduction repo?

test('raf', done => {
  requestAnimationFrame(() => {
    expect(true).toBe(true);

    done();
  });
});

passes, and switching true to false gives the failure as expected

fakeTimers doesn't work with raf (it's not implemented with the setTimeout we have access to), but that's a separate bug. Didn't think about that one

@SimenB yes, this example will work as expected, but if you will use raf inside some code, async done will not be available, so this code will not work:

test('raf',() => {
  requestAnimationFrame(() => {
    expect(true).toBe(true);
  });
});

so maybe fakeTimers issue fix will help

That's just async javascript for you.

You can open up a separate feature request for fakeTimers to work with raf (PR welcome as well!), but closing this as raf does work as it should in Jest 22

@SimenB thank you for help, have a nice day :-)

function myFunction() {
  requestAnimationFrame(() => {
    console.log('inner');
  });
}

myFunction();
console.log('outer');

Logs

inner

There is no way for Jest to know that you have scheduled some async work inside your test and wait for it unless you signal it (either done callback or returning a Promise).

You can also use e.g. this:

test('something', () => {
  expect.hasAssertions();

  requestAnimationFrame(() => {
    expect(true).toBe(true);
  });
});

Then your test will fail.

@SimenB yes, you're right, but if you will use jest < 22, you definitely will use requestAnimationFrame polyfill, and useFakeTimers with runAllTimers will help you to execute code inside callback, so if somebody will update jest to latest version, that code will not work.

Hey, any updates on this? I'm also facing the same issue. I upgraded Jest to v22 and my test using requestAnimationFrame started failing. Is there any way to get around this? Thanks.

You can mock it if you want

@SimenB for some reason I can't seem to be able to mock out requestAnimationFrame used inside the raf package.
I'm currently on Jest 22.1.3.
Here are the things that I've tried

jest.spyOn(window, 'requestAnimationFrame').mockImplementation(cb => cb());

window.requestAnimationFrame = cb => cb()

Object.defineProperty(window, 'requestAnimationFrame', {
    writable: true,
    value: cb => {
      cb();
    }
});

Any ideas on what's happening here? I know it's not returning my mock because console.log(window.requestAnimationFrame.toString()) is different that my mock. I also tried to switch out window for global and it still doesn't seem to work.

Update: I realized I could just mock the raf module instead. DOH!

jest.mock('raf', () => {
  return jest.fn().mockImplementation(cb => cb());
});

This works for me.

@manatarms JSDOM implements raf, so raf shouldn't do anything... I just tested, and using jest.spyOn works. See https://repl.it/@SimenB/ExtralargeRoyalblueNetwork

Thanks for the repl @SimenB. For some reason when I put a console.log(raf.toString()) inside /node_modules/raf/index.js for the return value, I get the original implementation instead of my mock. I could be doing something wrong here, but I got it working by mocking out raf instead of requestAnimationFrame.

I can replace requestAnimationFrame with my own implementation like this
(bit naive, but fine if I just want to advance timers)

global.requestAnimationFrame = fn => setTimeout(fn, 16);

But it must go inside setup-jest file, as if its inside my test file, it doesn't work.
(probably global is different between the two, I'm running JSDOM 15 and jest 24.8.0)

(I have used this approach to test react-spring components and it works pretty well)

you can just await a Promise

it("raf", async () => {
  await new Promise((resolve) =>
    requestAnimationFrame(() => {
      expect(true).toBe(true);
      resolve();
    })
  )
})

Sorry for commenting on this closed issue, but I don't fully understand why requestAnimationFrame needs to be mocked in one of the setup files first when the testEnvironment is jsdom? As soon as it's mocked in there, it can be mocked again within the tests, but without the mock in the setup file, the mock in the test file gets more or less ignored. Is there any reason for this behavior?

Was this page helpful?
0 / 5 - 0 ratings