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
我的组件:
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
语句之外和之后,那么它只会 运行 一次。断言 运行.
为什么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