React 测试库不更新状态

Jav*_*kie 4 unit-testing reactjs react-testing-library react-hooks

我的组件:

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
Run Code Online (Sandbox Code Playgroud)

我的测试文件:

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");
  });
});

Run Code Online (Sandbox Code Playgroud)

运行测试文件后,我收到错误消息:

Expected element to have text content:                                                          
      1                                                                                             
Received:                                                                                       
      0
Run Code Online (Sandbox Code Playgroud)

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

React-testing-lib 版本 9.3.2

sli*_*wp2 11

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

\n

现在,我们使用waitFor

\n
\n

waitFor可能会多次运行回调,直到达到超时。

\n
\n

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

\n

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

\n

RTL 使用MutationObserver来监视对 DOM 树所做的更改,请参阅此处。记住,我们的测试环境是jsdom,它支持MutationObserver,看这里

\n

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

\n

所以工作示例应该是:

\n

index.tsx

\n
import React from \'react\';\n\nconst TestAsync = () => {\n  const [counter, setCounter] = React.useState(0);\n\n  const delayCount = () =>\n    setTimeout(() => {\n      setCounter(counter + 1);\n    }, 500);\n\n  return (\n    <>\n      <h1 data-testid="counter">{counter}</h1>\n      <button data-testid="button-up" onClick={delayCount}>\n        Up\n      </button>\n      <button data-testid="button-down" onClick={() => setCounter(counter - 1)}>\n        Down\n      </button>\n    </>\n  );\n};\n\nexport default TestAsync;\n
Run Code Online (Sandbox Code Playgroud)\n

index.test.tsx

\n
import { fireEvent, render, waitFor } from \'@testing-library/react\';\nimport React from \'react\';\nimport TestAsync from \'.\';\nimport \'@testing-library/jest-dom/extend-expect\';\n\ndescribe(\'Test async\', () => {\n  it(\'increments counter after 0.5s\', async () => {\n    const { getByTestId } = render(<TestAsync />);\n\n    fireEvent.click(getByTestId(\'button-up\'));\n\n    await waitFor(() => {\n      const counter = getByTestId(\'counter\');\n      expect(counter).toHaveTextContent(\'1\');\n    });\n  });\n});\n
Run Code Online (Sandbox Code Playgroud)\n

测试结果:

\n
import React from \'react\';\n\nconst TestAsync = () => {\n  const [counter, setCounter] = React.useState(0);\n\n  const delayCount = () =>\n    setTimeout(() => {\n      setCounter(counter + 1);\n    }, 500);\n\n  return (\n    <>\n      <h1 data-testid="counter">{counter}</h1>\n      <button data-testid="button-up" onClick={delayCount}>\n        Up\n      </button>\n      <button data-testid="button-down" onClick={() => setCounter(counter - 1)}>\n        Down\n      </button>\n    </>\n  );\n};\n\nexport default TestAsync;\n
Run Code Online (Sandbox Code Playgroud)\n