【问题标题】:Make Unit Test Wait for Data filled by Asynchronous Fetch使单元测试等待异步获取填充的数据
【发布时间】:2021-09-10 13:25:50
【问题描述】:

我有一个单元测试,其中包括渲染一个使用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 unit-testing jestjs react-testing-library swr


【解决方案1】:

虽然我认为你已经在这样做了,但首先要注意的是你不应该实际上从你的测试中获取任何数据——你应该模拟结果。

一旦你这样做了,你将使用waitFor 实用程序来帮助你进行异步测试——这个实用程序基本上接受一个返回期望的函数 (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
    });
});

这未经测试,但这样的东西应该可以工作。您的 mocking 方法可能会因您构建 fetch 的方式而有所不同。

【讨论】:

  • 如果结果是来自我的快递服务器的模拟数据怎么办?这意味着我从服务器获取数据并在使用模拟数据的组件中呈现模拟数据。
  • @emre-ozgun 就个人而言,我会避免这样的事情。据我了解单元测试的指导原则,您希望最大限度地减少任何潜在变量,目标是尽可能隔离您正在测试的单元,以确保您正在测试单元和单独的单元,而不会产生任何复杂性从外部条件。此外,它将测试数据和测试保存在同一位置,以便于维护时的可读性。我并不是说没有可能的情况下您的设置是有效的,但我不确定它会是什么。
  • 我决定使用 cypress 来抽象出这种情况的限制。我想它更适合集成/e2e。例如,我仍然可以对我的表单(登录)进行“单元测试”。你认为我偏离了单元测试吗?我见过其他人也使用 cypress 来应用单元测试。
  • @emre-ozgun - 老实说,我没有使用 Cypress,但我的理解和你的一样——本质上它更像是一个集成/e2e 测试。需要明确的是,这也很重要——集成/e2e 测试是工具带中的另一个工具,可确保将功能正常、无错误的代码部署到生产环境中。根据我的经验,它通常是单元测试、集成/e2e 测试和人工测试/冒烟测试的组合,用于尝试保持代码质量。你需要找到自己的平衡点,但你正在测试是一个好的开始:)
【解决方案2】:

对于 ReactJS 问题,我在其他地方也有类似的答案,Web Fetch API (waiting the fetch to complete and then executed the next instruction)。让我为您的问题找出解决方案。

如果您的函数renderWithMockSwr() 是异步的,那么如果您希望它在调用下一行之前等待完成执行,请使用await 命令。

await renderWithMockSwr(
    <StyleOptionDetails
        {...mockIStyleOptionDetailsProps}
    />
)

async 很棒。 await 也是如此。看看吧:Mozilla Developer Network: Async Function

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-06-01
    • 1970-01-01
    • 2014-07-20
    • 1970-01-01
    • 1970-01-01
    • 2021-03-25
    • 2019-06-14
    • 2022-07-06
    相关资源
    最近更新 更多