如何测试由 useEffect 触发的 SetTimeout 修改的组件样式?

How can I test a component style modified by a SetTimeout fired on useEffect?

我正在使用 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,如 here or here

还有很多其他方法...但我总是得到

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

Expected: true
Received: false

关于如何在超时后正确获取可见性更改的任何想法? 谢谢!

组件挂载时会执行useEffectsetTimeout,挂载组件前需要使用jest.useFakeTimers(implementation?: 'modern' | 'legacy')

使用jest.runOnlyPendingTimers()

Executes only the macro-tasks that are currently pending (i.e., only the tasks that have been queued by setTimeout() or setInterval() up to this point)

jest.advanceTimersByTime(msToRun).

此外,我们需要将呈现它的代码包装起来并在 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",