使单元测试等待异步获取的数据填充

7

我有一个单元测试,其中包括呈现使用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);

如何正确创建延迟或等待数据?


嘿,我有一个答案可能会对你有所帮助,Web Fetch API(等待获取完成,然后执行下一条指令)。祝你好运。 - HoldOffHunger
2
请使用 findBy 或 waitFor。https://testing-library.com/docs/dom-testing-library/api-async/ - WebbH
你也可以将测试用例设置为“async”,并在异步语句之前使用“await”。 - edison16029
很可能在测试期间未达到该预期 - 测试通过并不会告诉您任何有用的信息。请阅读https://jestjs.io/docs/asynchronous。 - jonrsharpe
感谢大家的建议!将测试设置为“async”,并使用“await findBy”而不是“getBy”就解决了问题。如果您愿意回答这个问题,我会将其标记为答案 - 我猜先回答的人是 @HoldOffHunger @WebbH @edison16029。 - DLO
2个回答

3

虽然我认为您已经在这样做了,但需要注意的第一件事是,您不应该从测试中实际获取任何数据-- 您应该模拟结果。

一旦你这样做了,你将使用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)。


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

1

我在回答一个ReactJS问题时已经提供了类似的答案,Web Fetch API (等待fetch完成后执行下一条指令)。让我详细解决你的问题。

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

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

async 是很棒的。await 也是如此。看看这个:Mozilla 开发者网络:异步函数


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接