防止 "not wrapped in act(...)" 状态更新不影响时的开玩笑警告 UI
Preventing "not wrapped in act(...)" Jest warning when state update doesn't affect UI
我想弄清楚是否有一种方法可以防止 Jest/testing-library 在状态更新后我没有什么可断言时抛出的“未包装在 act(...)”警告导致警告发生,或者我是否应该忽略此警告。
假设我有这个简单的组件:
import React, {useEffect, useState} from 'react';
import {getData} from 'services';
const MyComponent = () => {
const [arr, setArr] = useState([]);
useEffect(() => {
(async () => {
const {items} = await getData();
setArr(items);
})();
}, []);
return (
<div>
{!(arr.length > 0) && <p>no array items</p>}
{arr.length > 0 && (
<ul>
{arr.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
)}
</div>
);
};
export default MyComponent;
假设我想简单地测试一下即使 getData()
没有 return 我的任何数据,这个组件也能正常渲染。
所以我有这样的测试:
import React from 'react';
import {getData} from 'services';
import {render, screen} from 'testUtils';
import MyComponent from './MyComponent';
jest.mock('services', () => ({
getData: jest.fn(),
}));
it('renders', () => {
getData.mockResolvedValue({items: []});
render(<MyComponent />);
expect(screen.getByText('no array items')).toBeInTheDocument();
});
这个测试会通过,但是我会收到“not wrapped in act(...)”警告,因为测试会在 getData()
有机会完成之前完成。
在这种情况下,来自 getData()
的响应将 arr
设置为相同的值(一个空数组),因为我最初将其设置在组件的顶部。因此,我的 UI 在异步函数完成后并没有改变——我仍然只是在看一段写着“没有数组项”的段落——所以我真的没有什么可以断言会等待状态更新完成。
我可以 expect(getData).toHaveBeenCalledTimes(1)
,但这不会等待状态在函数调用后实际更新。
我试图在测试中任意暂停,以便有时间 setArr(items)
发生:
it('renders', async () => {
getData.mockResolvedValue({items: []});
render(<MyComponent />);
expect(screen.getByText('no array items')).toBeInTheDocument();
await new Promise(resolve => setTimeout(resolve, 2000));
expect(screen.getByText('no array items')).toBeInTheDocument();
});
但这似乎没有帮助,老实说我不确定为什么。
有没有办法只修改测试来处理这种情况?
我确信我可以通过重构 MyComponent 来解决这个问题,例如,通过将 arr
作为 prop 传递给 MyComponent 并将 getData()
调用移动到父组件,或者创建一些自定义 prop仅用于完全跳过 getData()
调用的测试,但我不想纯粹为了避免测试中出现警告而修改组件。
我正在使用 testing-library/react,v11.2.2.
您可以使用 findByText
(getByText
和 waitFor
的组合)来确保在断言解决时所有更新都已发生。
it('renders', async () => {
getData.mockResolvedValue({items: []});
render(<MyComponent />);
expect(await screen.findByText('no array items')).toBeInTheDocument();
});
@juliomalves 的回答很准确。
但是,我不得不把这个 await
放在我的 beforeEach
:
import {render, fireEvent} from '@testing-library/react';
import MyComponent from './MyComponent';
...
describe('MyComponent should', () => {
let getByText, getByTestId, getAllByTestId, getByLabelText;
beforeEach(async () => {
let findByText;
({
getByText,
getByTestId,
getAllByTestId,
getByLabelText,
findByText,
} = render(<MyComponent {...props} />));
// Have this here to avoid warnings because of setting state variables
await findByText('no array items');
})
...
});
我想弄清楚是否有一种方法可以防止 Jest/testing-library 在状态更新后我没有什么可断言时抛出的“未包装在 act(...)”警告导致警告发生,或者我是否应该忽略此警告。
假设我有这个简单的组件:
import React, {useEffect, useState} from 'react';
import {getData} from 'services';
const MyComponent = () => {
const [arr, setArr] = useState([]);
useEffect(() => {
(async () => {
const {items} = await getData();
setArr(items);
})();
}, []);
return (
<div>
{!(arr.length > 0) && <p>no array items</p>}
{arr.length > 0 && (
<ul>
{arr.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
)}
</div>
);
};
export default MyComponent;
假设我想简单地测试一下即使 getData()
没有 return 我的任何数据,这个组件也能正常渲染。
所以我有这样的测试:
import React from 'react';
import {getData} from 'services';
import {render, screen} from 'testUtils';
import MyComponent from './MyComponent';
jest.mock('services', () => ({
getData: jest.fn(),
}));
it('renders', () => {
getData.mockResolvedValue({items: []});
render(<MyComponent />);
expect(screen.getByText('no array items')).toBeInTheDocument();
});
这个测试会通过,但是我会收到“not wrapped in act(...)”警告,因为测试会在 getData()
有机会完成之前完成。
在这种情况下,来自 getData()
的响应将 arr
设置为相同的值(一个空数组),因为我最初将其设置在组件的顶部。因此,我的 UI 在异步函数完成后并没有改变——我仍然只是在看一段写着“没有数组项”的段落——所以我真的没有什么可以断言会等待状态更新完成。
我可以 expect(getData).toHaveBeenCalledTimes(1)
,但这不会等待状态在函数调用后实际更新。
我试图在测试中任意暂停,以便有时间 setArr(items)
发生:
it('renders', async () => {
getData.mockResolvedValue({items: []});
render(<MyComponent />);
expect(screen.getByText('no array items')).toBeInTheDocument();
await new Promise(resolve => setTimeout(resolve, 2000));
expect(screen.getByText('no array items')).toBeInTheDocument();
});
但这似乎没有帮助,老实说我不确定为什么。
有没有办法只修改测试来处理这种情况?
我确信我可以通过重构 MyComponent 来解决这个问题,例如,通过将 arr
作为 prop 传递给 MyComponent 并将 getData()
调用移动到父组件,或者创建一些自定义 prop仅用于完全跳过 getData()
调用的测试,但我不想纯粹为了避免测试中出现警告而修改组件。
我正在使用 testing-library/react,v11.2.2.
您可以使用 findByText
(getByText
和 waitFor
的组合)来确保在断言解决时所有更新都已发生。
it('renders', async () => {
getData.mockResolvedValue({items: []});
render(<MyComponent />);
expect(await screen.findByText('no array items')).toBeInTheDocument();
});
@juliomalves 的回答很准确。
但是,我不得不把这个 await
放在我的 beforeEach
:
import {render, fireEvent} from '@testing-library/react';
import MyComponent from './MyComponent';
...
describe('MyComponent should', () => {
let getByText, getByTestId, getAllByTestId, getByLabelText;
beforeEach(async () => {
let findByText;
({
getByText,
getByTestId,
getAllByTestId,
getByLabelText,
findByText,
} = render(<MyComponent {...props} />));
// Have this here to avoid warnings because of setting state variables
await findByText('no array items');
})
...
});