fac-13 / HP-game

https://relaxed-wing-98fffd.netlify.com/
0 stars 2 forks source link

Testing with setTimeout #9

Open helenzhou6 opened 6 years ago

helenzhou6 commented 6 years ago

Not sure if you managed to get round to it, but you can pass in done() as a callback in jest:

it('mock setTimeout test', (done) => {
  setTimeout(() => {
    console.log('TIME IS UP');
    done();
  }, 1000);
});

(Thanks for this post)

oliverjam commented 6 years ago

You're probably not working on this anymore, but I just had to write a test for a time-based function at work so I thought I'd drop this in in case you hadn't seen it: https://facebook.github.io/jest/docs/en/timer-mocks.html

You can mock timers in Jest so you don't actually have to wait

iPhatty commented 6 years ago

Thanks buddy! I was trying to play with the jest faketimers and couldn't fully figure it out, but I will definitely get one done eventually!

oliverjam commented 6 years ago

I think you can do

jest.useFakeTimers();
jest.advanceTimersByTime(10000); // or however much time you need to have passed

and any timeouts etc in your code should have run.

helenzhou6 commented 6 years ago

I just spent a good while trying to use jest.useFakeTimers(); jest.advanceTimersByTime(10000); and the methods listed here with no luck 😅

I found the article is testing a function that has a simple timer with a callback, and it tests whether the callback function has been called or not, which seems different to our function (where we just want certain things to run after a set time).

https://github.com/fac-13/HP-game/blob/ae8c093f374a28aa81747297129b67c1870cb093/src/components/timeGame/timeGame.test.js#L14-L22

oliverjam commented 6 years ago

Okay I figured it out. The main problem was that apparently Jest's fake timers don't handle the Date object. Luckily it's quite easy to mock if you're just using Date.now().

So what we need to do is mock Date.now() to return 0 the first time (so the component's start time is 0), then always return 10000 after that.

The second key is jest.runOnlyPendingTimers(), which will run whatever timers are queued without allowing anymore to start. This seems to avoid a weird issue I was getting where the interval ran 10000 times and then Jest gave up.

So I think what's happening is:

  1. Jest hijacks the timers
  2. Date.now() is mocked
  3. Start button is clicked
  4. Date.now() gets called once and start time set to 0 in the component
  5. Timer is created with setInterval in the component
  6. jest.runOnlyPendingTimers() lets the interval run once
  7. Date.now() returns 10000
  8. Stop button is clicked
  9. Component's render method sees newTime is 10000
  10. Success 🎉
test('test TimeGame component win', () => {
  jest.useFakeTimers(); // tell Jest to hijack JS timers
  Date.now = jest
    .fn() // create the mock function
    .mockReturnValueOnce(0) // tell the mock to return 0 only the first time it's called
    .mockReturnValue(10000); // tell it to return 10000 every other time

  const { getByText, getByTestId } = renderIntoDocument(<TimeGame />);

  const startButton = getByText('Start');
  fireEvent.click(startButton);

  jest.runOnlyPendingTimers(); // allow the created interval to run once

  const stopButton = getByText('Stop');
  fireEvent.click(stopButton);

  const message = getByTestId('message');
  expect(message.textContent).toEqual('WOW you got it spot on!');
});
helenzhou6 commented 6 years ago

That is absolutely awesome @oliverjam - and thanks for the explanation too 😍