让单元测试等待Asynchronous Fetch填充的数据
Make Unit Test Wait for Data filled by Asynchronous Fetch
我有一个单元测试,其中包括渲染一个使用 useSWR
获取数据的组件。但是在调用 expect()
之前数据还没有准备好,所以测试失败。
test("StyleOptionDetails contains correct style option number", () => {
renderWithMockSwr(
<StyleOptionDetails
{...mockIStyleOptionDetailsProps}
/>
)
expect(screen.getByText(123)).toBeInTheDocument();
});
但如果我延迟 setTimeout()
,它会通过测试。
setTimeout(() => {
console.log('This will run after 2 second')
expect(screen.getByText(123)).toBeInTheDocument();
}, 2000);
创建延迟或等待数据的正确方法是什么?
我在其他地方对 ReactJS 问题有类似的回答,。让我针对您的问题制定解决方案。
如果您的函数 renderWithMockSwr()
是异步的,那么如果您希望它在调用下一行之前等待完成执行,请使用 await
命令。
await renderWithMockSwr(
<StyleOptionDetails
{...mockIStyleOptionDetailsProps}
/>
)
async
太棒了。 await
也是。看看:Mozilla Developer Network: Async Function
虽然我认为你已经在这样做了,但首先要注意的是你不应该实际上获取任何数据从你的测试中——你应该模拟结果。
一旦你这样做了,你将使用 waitFor
实用程序来帮助你进行异步测试——这个实用程序基本上接受一个 returns 期望的函数 (expect
) , 并将保持在测试的那个点,直到达到预期。
让我们举个例子。以我在下面创建的虚构组件为例:
const MyComponent = () => {
const [data, setData] = useState();
useEffect(() => {
MyService.fetchData().then((response) => setData(response));
}, []);
if (!data) {
return (<p>Loading...</p>);
}
// else
return (
<div>
<h1>DATA!</h1>
<div>
{data.map((datum) => (<p>{datum}</p>))}
</div>
</div>
);
}
所以对于你的测试,你会做
import MyService from './MyService';
import MyComponent from './MyComponent';
describe('MyComponent', () => {
const mockData = ['Spengler', 'Stanz', 'Venkman', 'Zeddmore'];
beforeEach(() => {
jest.spyOn(MyService, 'fetchData')
.mockImplementation(
() => new Promise((res) => setTimeout(() => res(mockData), 200))
);
});
afterEach(() => {
MyService.fetchData.mockRestore();
});
it('displays the loading first and the data once fetched', async () => {
render(<MyComponent />);
// before fetch completes
expect(screen.getByText(/Loading/)).toBeInTheDocument();
expect(screen.queryByText('DATA!')).toBeNull();
// after fetch completes..
// await waitFor will wait here for this to be true; if it doesn't happen after about five seconds the test fails
await waitFor(() => expect(screen.getByText('DATA!')).toBeInTheDocument());
expect(screen.queryByText(/Loading/)).toBeNull(); // we don't have to await this one because we awaited the proper condition in the previous line
});
});
这没有经过测试,但像这样的东西应该可以工作。您的模拟方法可能会因您构建提取的方式而有所不同。
我有一个单元测试,其中包括渲染一个使用 useSWR
获取数据的组件。但是在调用 expect()
之前数据还没有准备好,所以测试失败。
test("StyleOptionDetails contains correct style option number", () => {
renderWithMockSwr(
<StyleOptionDetails
{...mockIStyleOptionDetailsProps}
/>
)
expect(screen.getByText(123)).toBeInTheDocument();
});
但如果我延迟 setTimeout()
,它会通过测试。
setTimeout(() => {
console.log('This will run after 2 second')
expect(screen.getByText(123)).toBeInTheDocument();
}, 2000);
创建延迟或等待数据的正确方法是什么?
我在其他地方对 ReactJS 问题有类似的回答,
如果您的函数 renderWithMockSwr()
是异步的,那么如果您希望它在调用下一行之前等待完成执行,请使用 await
命令。
await renderWithMockSwr(
<StyleOptionDetails
{...mockIStyleOptionDetailsProps}
/>
)
async
太棒了。 await
也是。看看:Mozilla Developer Network: Async Function
虽然我认为你已经在这样做了,但首先要注意的是你不应该实际上获取任何数据从你的测试中——你应该模拟结果。
一旦你这样做了,你将使用 waitFor
实用程序来帮助你进行异步测试——这个实用程序基本上接受一个 returns 期望的函数 (expect
) , 并将保持在测试的那个点,直到达到预期。
让我们举个例子。以我在下面创建的虚构组件为例:
const MyComponent = () => {
const [data, setData] = useState();
useEffect(() => {
MyService.fetchData().then((response) => setData(response));
}, []);
if (!data) {
return (<p>Loading...</p>);
}
// else
return (
<div>
<h1>DATA!</h1>
<div>
{data.map((datum) => (<p>{datum}</p>))}
</div>
</div>
);
}
所以对于你的测试,你会做
import MyService from './MyService';
import MyComponent from './MyComponent';
describe('MyComponent', () => {
const mockData = ['Spengler', 'Stanz', 'Venkman', 'Zeddmore'];
beforeEach(() => {
jest.spyOn(MyService, 'fetchData')
.mockImplementation(
() => new Promise((res) => setTimeout(() => res(mockData), 200))
);
});
afterEach(() => {
MyService.fetchData.mockRestore();
});
it('displays the loading first and the data once fetched', async () => {
render(<MyComponent />);
// before fetch completes
expect(screen.getByText(/Loading/)).toBeInTheDocument();
expect(screen.queryByText('DATA!')).toBeNull();
// after fetch completes..
// await waitFor will wait here for this to be true; if it doesn't happen after about five seconds the test fails
await waitFor(() => expect(screen.getByText('DATA!')).toBeInTheDocument());
expect(screen.queryByText(/Loading/)).toBeNull(); // we don't have to await this one because we awaited the proper condition in the previous line
});
});
这没有经过测试,但像这样的东西应该可以工作。您的模拟方法可能会因您构建提取的方式而有所不同。