最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - useFakeTimers not working in jesttesting-library - Stack Overflow

programmeradmin6浏览0评论

I'm rendering an element that makes use of a setTimeout to change the inner text from a loading state to a desired message:

function Message({ message }: any) {
  const [showMessage, setShowMessage] = useState(false);

  useEffect(() => {
    const CTATimer = setTimeout(() => {
      setShowMessage(true);
    }, 1500);
    return () => {
      clearTimeout(CTATimer);
    };
  }, []);

  if (!showMessage) {
    return <p>Loading...</p>;
  }

  return (
    <>
      <div>{message.text}</div>
    </>
  );
}

The corresponding test renders, then advances time by 1500ms, and then should show the message. However, currently the test fails and the terminal shows that the text is still Loading.... The test is written like so:

const mockMessage = {
  text: "this is a message",
  answers: [],
  id: 1,
};

afterEach(() => {
  jest.useRealTimers();
});

it("should show message after setTimeout", () => {
  jest.useFakeTimers();
  jest.advanceTimersByTime(1500);
  customRender(<Message message={mockMessage} />); // my customRender is just the default render but with a ThemeProvider wrapper.
  const message = screen.getByText(/this is a message/i);
  expect(message).toBeInTheDocument();
});

Why would my test still be rendering the loading state when 1500ms have passed?

I'm rendering an element that makes use of a setTimeout to change the inner text from a loading state to a desired message:

function Message({ message }: any) {
  const [showMessage, setShowMessage] = useState(false);

  useEffect(() => {
    const CTATimer = setTimeout(() => {
      setShowMessage(true);
    }, 1500);
    return () => {
      clearTimeout(CTATimer);
    };
  }, []);

  if (!showMessage) {
    return <p>Loading...</p>;
  }

  return (
    <>
      <div>{message.text}</div>
    </>
  );
}

The corresponding test renders, then advances time by 1500ms, and then should show the message. However, currently the test fails and the terminal shows that the text is still Loading.... The test is written like so:

const mockMessage = {
  text: "this is a message",
  answers: [],
  id: 1,
};

afterEach(() => {
  jest.useRealTimers();
});

it("should show message after setTimeout", () => {
  jest.useFakeTimers();
  jest.advanceTimersByTime(1500);
  customRender(<Message message={mockMessage} />); // my customRender is just the default render but with a ThemeProvider wrapper.
  const message = screen.getByText(/this is a message/i);
  expect(message).toBeInTheDocument();
});

Why would my test still be rendering the loading state when 1500ms have passed?

Share Improve this question edited Feb 21, 2022 at 5:37 Lin Du 102k135 gold badges332 silver badges564 bronze badges asked Feb 18, 2022 at 13:13 crevuluscrevulus 2,4383 gold badges24 silver badges63 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 11

If working with an asynchronous test because you need to use userEvent for typing etc. I found a solution on this blog: https://onestepcode./testing-library-user-event-with-fake-timers/

The trick is to set the delay option on the userEvent to null.

const user = userEvent.setup({ delay: null });

Here is a full test case

test("Pressing the button hides the text (fake timers)", async () => {
    const user = userEvent.setup({ delay: null });
    jest.useFakeTimers();
    
    render(<Demo />);

    const button = screen.getByRole("button");
    await user.click(button);

    act(() => {
        jest.runAllTimers();
    });

    const text = screen.queryByText("Hello World!");
    expect(text).not.toBeInTheDocument();

    jest.useRealTimers();
});

You should advance timers after rendering the ponent. Besides, you should call jest.advanceTimersByTime() inside act function. Otherwise, it will throws an warning: Warning: An update to Message inside a test was not wrapped in act(...).

index.tsx:

import React from 'react';
import { useEffect, useState } from 'react';

export function Message({ message }: any) {
  const [showMessage, setShowMessage] = useState(false);

  useEffect(() => {
    const CTATimer = setTimeout(() => {
      setShowMessage(true);
    }, 1500);
    return () => {
      clearTimeout(CTATimer);
    };
  }, []);

  if (!showMessage) {
    return <p>Loading...</p>;
  }

  return (
    <>
      <div>{message.text}</div>
    </>
  );
}

index.test.tsx:

import React from 'react';
import { render, screen, act } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import { Message } from './';

describe('Message', () => {
  const mockMessage = {
    text: 'this is a message',
    answers: [],
    id: 1,
  };

  afterEach(() => {
    jest.useRealTimers();
  });

  it('should show message after setTimeout', () => {
    jest.useFakeTimers();
    render(<Message message={mockMessage} />);
    act(() => {
      jest.advanceTimersByTime(1500);
    });
    const message = screen.getByText(/this is a message/i);
    expect(message).toBeInTheDocument();
  });
});

Test result:

 PASS  stackoverflow/71174071/index.test.tsx (9.705 s)
  Message
    ✓ should show message after setTimeout (27 ms)

-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------|---------|----------|---------|---------|-------------------
All files  |     100 |      100 |     100 |     100 |                   
 index.tsx |     100 |      100 |     100 |     100 |                   
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        10.903 s
Ran all test suites related to changed files.
发布评论

评论列表(0)

  1. 暂无评论