React 测试库在初始渲染后未使用 getAllByTestId 找到元素

React Testing Library does not find elements using getAllByTestId after initial render

我有一个非常简单的组件,我在其中尝试模拟 API 调用以稍微延迟获取一些电影。

我想编写一个测试来测试电影是否被收集然后渲染到屏幕上。

我正在尝试使用 screen.getAllByTestId 来执行此操作,但它总是失败。就好像它没有重新渲染,因此没有得到更新的变化。

我在元素上添加了一个 testid,可以在 DOM.

中看到它们

任何人都可以帮助解释为什么在他们出现后找不到他们吗?

这是完整的组件代码...

import './App.css';
import { useEffect, useState } from 'react';

function App() {
  const [movies, setMovies] = useState([]);

  useEffect(() => {
    // simulate API call to get
    setTimeout(() => {
      const movies = [{ title: 'Titanic' }, { title: 'Back To The Future' }];
      setMovies(movies);
    }, 1000);
  }, []);

  return (
    <div>
      {movies.length > 0 && (
        <div>
          {movies.map((x) => (
            <div data-testid='movies'>{x.title}</div>
          ))}
        </div>
      )}
    </div>
  );
}

export default App;

这是完整的测试代码...

import { render, screen } from '@testing-library/react';
import App from './App';

test('renders learn react link', () => {
  render(<App />);
  const movieTiles = screen.getAllByTestId('movies');
  expect(movieTiles).toHaveLength(2);
});

这是测试的错误

当您的代码使用计时器时,您应该使用 Fake TimerssetTimeoutsetIntervalclearTimeoutclearInterval)。

使用jest.advanceTimersByTime(1000)时间提前1000ms。

别忘了act()辅助函数:

When writing UI tests, tasks like rendering, user events, or data fetching can be considered as “units” of interaction with a user interface. react-dom/test-utils provides a helper called act() that makes sure all updates related to these “units” have been processed and applied to the DOM before you make any assertions:

因为我们运行setState函数会提前1000ms改变组件状态,我们必须把这个操作(jest.advanceTimersByTime(1000))包装在act()函数中.

否则,您将收到警告:

Warning: An update to App inside a test was not wrapped in act(...).

When testing, code that causes React state updates should be wrapped into act(...):

例如

App.jsx:

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

function App() {
  const [movies, setMovies] = useState([]);

  useEffect(() => {
    setTimeout(() => {
      const movies = [{ title: 'Titanic' }, { title: 'Back To The Future' }];
      setMovies(movies);
    }, 1000 * 10);
  }, []);

  return (
    <div>
      {movies.length > 0 && (
        <div>
          {movies.map((x, idx) => (
            <div key={idx} data-testid="movies">
              {x.title}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

export default App;

App.test.jsx:

import { render, screen, act } from '@testing-library/react';
import React from 'react';
import App from './App';

describe('68460159', () => {
  test('renders learn react link', async () => {
    jest.useFakeTimers();
    render(<App />);
    act(() => {
      jest.advanceTimersByTime(1000 * 10);
    });
    const movieTiles = screen.getAllByTestId('movies');
    expect(movieTiles).toHaveLength(2);
    jest.runOnlyPendingTimers();
    jest.useRealTimers();
  });
});

测试结果:

 PASS  examples/68460159/App.test.jsx (7.878 s)
  68460159
    ✓ renders learn react link (33 ms)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |     100 |      100 |     100 |     100 |                   
 App.jsx  |     100 |      100 |     100 |     100 |                   
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        8.678 s, estimated 10 s