首页 > 解决方案 > 如何测试由在 useEffect 上触发的 SetTimeout 修改的组件样式?

问题描述

我正在使用 Jest/Enzyme 来测试一个 React/TypeScript 应用程序,我很难编写一个测试来断言某个按钮是否在一段时间后出现:

这是要测试的组件的一个非常简化的版本:

import { StyledNotifyButton } from './styles'; //style-component element

const SomeComponent = (): ReactElement => {
  const [showNotifyButton, toggleNotifyButton] = useState(false);

  useEffect(() => {
    setTimeout(() => toggleNotifyButton(true), 5000);
  }, [toggleNotifyButton]);

  return (
    <div>
      <StyledNotifyButton visible={showNotifyButton} />
    </div>
  );

这是测试:

 describe('< FailingTest >', () => {
  let wrapper: ReactWrapper;

  beforeAll(() => {
    wrapper = mount(<SomeComponent />);
  });

  it('should display the notify button only after X seconds', done => {
    let notifyButton = wrapper.find('StyledNotifyButton');

    jest.spyOn(React, 'useEffect').mockImplementation(f => f());
    expect(notifyButton.prop('visible')).toBe(false);

    jest.useFakeTimers();
    setTimeout(() => {
      wrapper.update();
      notifyButton = wrapper.find('NotifyButton');
      expect(notifyButton.prop('visible')).toBe(true);
      wrapper.unmount();
      done();
    }, 5000);
    jest.runAllTimers();
  });

我已经尝试使用 fakeTimers、advanceTimersByTime、runAllTimers,如Just Timer Mocks 中所述

我已经尝试过 setTimeouts,如此处所述

手动触发 useEffect,如此此处

还有许多其他方式......但我总是得到

expect(received).toBe(expected) // Object.is equality

Expected: true
Received: false

关于如何在超时后正确获得可​​见性变化的任何想法?谢谢!

标签: reactjsjestjsreact-hooksenzymets-jest

解决方案


当组件被挂载useEffect并将setTimeout被执行时,你需要在挂载组件之前使用jest.useFakeTimers(implementation?: 'modern' | 'legacy')

使用jest.runOnlyPendingTimers()

仅执行当前挂起的宏任务(即,仅执行到目前为止已由 setTimeout() 或 setInterval() 排队的任务)

jest.advanceTimersByTime(msT​​oRun)

此外,我们需要在act()调用中包装渲染它并执行更新的代码,所以jest.runOnlyPendingTimers()放入act().

最后,我们需要调用wrapper.update()以确保状态反映到视图中。

例如

SomeComponent.tsx

import React, { ReactElement, useEffect, useState } from 'react';
import { StyledNotifyButton } from './styles';

export const SomeComponent = (): ReactElement => {
  const [showNotifyButton, toggleNotifyButton] = useState(false);

  useEffect(() => {
    setTimeout(() => {
      toggleNotifyButton(true);
    }, 5000);
  }, [toggleNotifyButton]);

  console.log('showNotifyButton: ', showNotifyButton);

  return (
    <div>
      <StyledNotifyButton visible={showNotifyButton} />
    </div>
  );
};

styles.tsx

import React from 'react';

export function StyledNotifyButton({ visible }) {
  return <button>click me</button>;
}

SomeComponent.test.tsx

import { mount, ReactWrapper } from 'enzyme';
import React from 'react';
import { act } from 'react-dom/test-utils';
import { SomeComponent } from './SomeComponent';

describe('67440874', () => {
  let wrapper: ReactWrapper;

  beforeAll(() => {
    jest.useFakeTimers();
    wrapper = mount(<SomeComponent />);
  });
  it('should pass', () => {
    let notifyButton = wrapper.find('StyledNotifyButton');
    expect(notifyButton.prop('visible')).toBe(false);
    act(() => {
      jest.runOnlyPendingTimers();
    });
    wrapper.update();
    expect(wrapper.find('StyledNotifyButton').prop('visible')).toBeTruthy();
  });
});

测试结果:

 PASS  examples/67440874/SomeComponent.test.tsx
  67440874
    ✓ should pass (8 ms)

  console.log
    showNotifyButton:  false

      at SomeComponent (examples/67440874/SomeComponent.tsx:13:11)

  console.log
    showNotifyButton:  true

      at SomeComponent (examples/67440874/SomeComponent.tsx:13:11)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.721 s

包版本:

"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.5",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"jest": "^26.6.3",
"jest-environment-enzyme": "^7.1.2",
"jest-enzyme": "^7.1.2",

推荐阅读