React 测试库不更新状态

React testing lib not update the state

我的组件:

import React from 'react'

const TestAsync = () => {
  const [counter, setCounter] = React.useState(0)

  const delayCount = () => (
    setTimeout(() => {
      setCounter(counter + 1)
    }, 500)
  )
  
return (
  <>
    <h1 data-testid="counter">{ counter }</h1>
    <button data-testid="button-up" onClick={delayCount}> Up</button>
    <button data-testid="button-down" onClick={() => setCounter(counter - 1)}>Down</button>
 </>
    )
  }
  
  export default TestAsync

我的测试文件:

describe("Test async", () => {
  it("increments counter after 0.5s", async () => {
    const { getByTestId, getByText } = render(<TestAsync />);

    fireEvent.click(getByTestId("button-up"));

    const counter = await waitForElement(() => getByTestId("counter"));

    expect(counter).toHaveTextContent("1");
  });
});

在 运行 测试文件之后,我得到错误说:

Expected element to have text content:                                                          
      1                                                                                             
Received:                                                                                       
      0

我有点困惑,为什么我使用 waitForElement 来获取元素,但为什么元素仍然具有旧值?

React-testing-lib 版本 9.3.2

首先,waitForElement 已被弃用。使用 find* 查询(首选:https://testing-library.com/docs/dom-testing-library/api-queries#findby) or use waitFor instead: https://testing-library.com/docs/dom-testing-library/api-async#waitfor

现在,我们使用 waitFor:

waitFor may run the callback a number of times until the timeout is reached.

您需要将断言语句包装在 waitFor 的回调中。这样waitFor可以运行回调多次。如果将 expect(counter).toHaveTextContent('1'); 语句放在 waitFor 语句之外和之后,那么它只会 运行 一次。断言 运行.

时 React 尚未更新

为什么RTL会运行回调多次(运行超时前每隔一段时间回调一次)?

RTL 使用 MutationObserver to watch for changes being made to the DOM tree, see here. Remember, our test environment is jsdom, it supports MutationObserver, see here.

这意味着当 React 更新状态并将更新应用于 DOM 时,可以检测到 DOM 树的更改并且 RTL 将再次 运行 回调,包括断言。当应用 React 组件状态并变得稳定时,回调的最后一个 运行 将作为测试的最终断言。如果断言失败,则报错,否则,测试通过。

所以工作示例应该是:

index.tsx:

import React from 'react';

const TestAsync = () => {
  const [counter, setCounter] = React.useState(0);

  const delayCount = () =>
    setTimeout(() => {
      setCounter(counter + 1);
    }, 500);

  return (
    <>
      <h1 data-testid="counter">{counter}</h1>
      <button data-testid="button-up" onClick={delayCount}>
        Up
      </button>
      <button data-testid="button-down" onClick={() => setCounter(counter - 1)}>
        Down
      </button>
    </>
  );
};

export default TestAsync;

index.test.tsx:

import { fireEvent, render, waitFor } from '@testing-library/react';
import React from 'react';
import TestAsync from '.';
import '@testing-library/jest-dom/extend-expect';

describe('Test async', () => {
  it('increments counter after 0.5s', async () => {
    const { getByTestId } = render(<TestAsync />);

    fireEvent.click(getByTestId('button-up'));

    await waitFor(() => {
      const counter = getByTestId('counter');
      expect(counter).toHaveTextContent('1');
    });
  });
});

测试结果:

 PASS  Whosebug/71639088/index.test.tsx
  Test async
    ✓ increments counter after 0.5s (540 ms)

-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------|---------|----------|---------|---------|-------------------
All files  |   88.89 |      100 |      75 |   88.89 |                   
 index.tsx |   88.89 |      100 |      75 |   88.89 | 17                
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.307 s