React 测试库的 waitFor 不起作用

IT技术 reactjs react-testing-library
2021-05-12 13:46:47

我正在使用React 测试库对我的 ReactJS 代码进行单元测试。UI 中有几个异步事件,例如获取数据并在单击按钮时显示新页面。React 代码有点像这样:

// Inside ParentComponent.tsx
const [isChildVisible, setChildVisibility] = useState(false);
const showChild = () => setChildVisibility(true);

return(
  <>
      <button data-testid="show-child" onClick={showChild}>Show Child</button>
      {isChildVisible && <ChildComponent {..childProps}/>}
 </>
)

ChildComponent挂载的地方,它会获取一些数据,然后使用水合数据重新渲染自身。我的单元测试看起来像:

jest.mock('../../../src/service'); // mock the fetch functions used by ChildComponent to fetch its data

describe('ParentComponent', () => {
    test('renders ChildComponent on button click', async () => {
        const screen = render(<ParentComponent />);
        userEvent.click(screen.getByTestId('show-child'));
        await (waitFor(() => screen.getByText('text rendered by child')));
    });
});

当我运行此测试时,出现错误"TestingLibraryElementError: Unable to find an element with the text: text rendered by child. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible."

我不确定为什么会发生这种情况,但原因之一可能是水合和渲染子组件需要一秒钟以上的时间。因此,我想更改 的默认等待时间waitFor,但我找不到从文档中执行此操作的方法(默认等待时间为一秒)。那么是否可以更改默认等待时间?

编辑:增加等待时间仍然导致相同的错误。所以问题是另一回事。

4个回答

我在这里找到了答案:React Testing Library - using 'await wait()' after fireEvent

TLDR:“您不能将等待与 getBy* 一起使用。getBy 不是异步的,不会等待。” 更好的是使用 findBy*。这是 getBy 的异步版本。

它在文档中指定。等待文档

function waitFor<T>(
  callback: () => T | Promise<T>,
  options?: {
     container?: HTMLElement
     timeout?: number //This is 1000ms. Change timeout here.
     interval?: number
     onTimeout?: (error: Error) => Error
     mutationObserverOptions?: MutationObserverInit
  }
): Promise<T>

//For 3 seconds.
await (waitFor(() => screen.getByText('text rendered by child'),{timeout:3000}));

默认超时为 1000 毫秒,这将使您保持在 Jest 的默认超时 5000 毫秒之下。

我在为测试应用程序设置测试时遇到了与此类似的问题。我解决这个问题的方法是强制重新渲染组件。

在这种情况下,您的代码将类似于:

import {render, screen} from "@testing-library/react";

describe('ParentComponent', () => {
  test('renders ChildComponent on button click', async () => {

    const {rerender} = render(<ParentComponent />);
    userEvent.click(screen.getByTestId('show-child'));

    rerender(<ParentComponent />)
    await (waitFor(() => screen.getByText('text rendered by child')));
  });
});

我希望这对你有用。还要注意的是,您可以使用screenreact 测试库中导出。似乎应该有一种方法可以自动执行此操作,但是我一直找不到。

添加到重新渲染文档的链接:https ://testing-library.com/docs/react-testing-library/api/#rerender

对于那些使用jest-expo破坏此功能的预设的人,您需要修改jest-expo预设以包含来自testing-library/react-native

/* eslint-disable @typescript-eslint/no-var-requires */
const { mergeDeepRight } = require("ramda");
const jestExpoPreset = require("jest-expo/jest-preset");
const testingLibraryPreset = require("@testing-library/react-native/jest-preset");

/*
 * Modify the existing jest preset to implement the fix of @testing-library/react-native to get the
 * async waitFor working with modern timers.
 */
jestExpoPreset.setupFiles = [
  testingLibraryPreset.setupFiles[0],
  ...jestExpoPreset.setupFiles,
  testingLibraryPreset.setupFiles[testingLibraryPreset.setupFiles.length - 1],
];
module.exports = mergeDeepRight(jestExpoPreset, {
  testResultsProcessor: "jest-sonar-reporter",
  moduleFileExtensions: ["js", "jsx", "ts", "tsx", "yml"],
  modulePathIgnorePatterns: ["<rootDir>/lib/"],
  globals: {
    "ts-jest": {
      babelConfig: "./babel.config.js",
    },
  },
});