如何使用异步函数和内部的 setState 测试 useEffect [英] How to test useEffect with async function and setState inside

查看:19
本文介绍了如何使用异步函数和内部的 setState 测试 useEffect的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经建立了一个 github 项目来了解如何更好地测试 react (v 16.8.0) useEffect 钩子.我调用 api 来获取 useEffect 内的数据,并将接收到的数据设置为状态组件元素.我的组件将查询作为道具接收,如果查询道具字符串不为空,则进行 api 调用.我想用一个非空的查询道具测试 api 调用是否已完成,并且组件将其状态设置为正确.

I have set up a github project to understand how to better test react (v 16.8.0) useEffect hook. I make an api call to fetch data inside useEffect and I set the received data as state component element. My component receives the query as a prop and make the api call if the query prop string is not empty. I would like to test that with a no-empty query prop the api call is made and the component set its state right.

我知道测试 useEffect 所面临的问题是与 useEffect 相关的效果不会阻止浏览器更新屏幕,因此测试来到他们的在 useEffect 发挥作用之前结束.我从 React 文档中了解到,react-test-utils 中有一个名为 act 的 API,它被认为可以包装呈现组件的代码并对其执行更新.即使我尝试使用它,我的代码仍然遇到同样的问题.

I know that the problem to be faced testing useEffect is that the effects related to useEffect don't block the browser from updating the screen so the tests come to their end before the useEffect makes its job. I read from React documentation that there is an API from react-test-utils called act that is thought to wrap the code rendering the component and performing updates on it. Even if I tried to use it I keep on having the same problems with my code.

这是我要测试的组件:

const DisplayData = ({ query, onQueryChange }) => {
    const [data, setData] = useState({ hits: [] });

    useEffect(() => {
        const fetchData = async () => {
            const result = await axios.get(
                `http://hn.algolia.com/api/v1/search?query=${query}`,
            );
            setData(result.data);
        };
        if (!!query) fetchData();
    }, [query]);

    return (
        <ul>
            {data.hits.map(item => (
                <li key={item.objectID}>
                    <a href={item.url}>{item.title}</a>
                </li>
            ))}
        </ul>
    );
};

这是我为它编写的测试:

and this is the test I wrote for it:

it("should show new entries when query is set", () => {
    const el = document.createElement("div");
    document.body.appendChild(el);
    axios.get.mockResolvedValue({ data: { hits: FAKE_HITS } });
    act(() => {
        render(<DisplayData query='pippo' />, el);
    });
    const liCounts = el.querySelectorAll("li");
    expect(liCounts.length).toBe(2);
});

我不断收到警告,告诉我

I keep on receiving a warning telling me that

测试中对 DisplayData 的更新未包含在 act(...)

我的测试失败了,因为 liCounts 收到的是 is_0_ 而不是预期的 2.

and my test failed because the liCounts received is_0_ instead of the expected 2.

插入相同的控制台消息来调试应用程序,我相信问题在于 useEffect 在测试执行后启动,但我不知道如何继续.

Inserting same console messages to debug the application, I trust that the problem is that useEffect is launched after the test execution but I don't know any more how to proceed.

更新感谢 @jonrsharpe,我使用 React 版本 16.9.0-alpha.0 解决了我的问题,该版本具有 act api 的异步版本.

UPDATE Thanks to @jonrsharpe I solved my issue using React version 16.9.0-alpha.0 that has an async version of act api.

推荐答案

这里是单元测试解决方案:

Here is the unit test solution:

我们使用 jest.spyOn(axios, 'get') 来模拟 axios.get 方法及其解析/拒绝值,而不会影响真实网络.这使得我们的单元测试可以在一个没有副作用的环境中运行,并且与系统环境、网络环境等隔离.

We use jest.spyOn(axios, 'get') to mock axios.get method and its resolved/rejected value without hitting the real network. This allows our unit tests to run in an environment that has no side effects and is isolated from the system environment, network environment, etc.

我们使用 act() 帮助器来确保获取的数据呈现并用户界面已更新.

We use act() helper to make sure the fetched data rendered and UI has been updated.

在编写 UI 测试时,渲染、用户事件或数据获取等任务可被视为与用户界面交互的单元".react-dom/test-utils 提供了一个名为 act() 的帮助程序,可确保在您做出任何断言之前,与这些单元"相关的所有更新都已处理并应用于 DOM:

When writing UI tests, tasks like rendering, user events, or data fetching can be considered as "units" of interaction with a user interface. react-dom/test-utils provides a helper called act() that makes sure all updates related to these "units" have been processed and applied to the DOM before you make any assertions:

最后,我们断言axios.get方法是否被调用,并通过快照测试断言data是否正确渲染

In the end, We assert whether the axios.get method is called, and through snapshot testing, assert whether data is rendered correctly

index.tsx:

import React, { useState, useEffect } from 'react';
import axios from 'axios';

export const DisplayData = ({ query, onQueryChange }) => {
  const [data, setData] = useState<any>({ hits: [] });

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios.get(`http://hn.algolia.com/api/v1/search?query=${query}`);
      setData(result.data);
    };
    if (!!query) fetchData();
  }, [query]);

  return (
    <ul>
      {data.hits.map(item => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
};

index.spec.tsx:

import React from 'react';
import { DisplayData } from './';
import axios from 'axios';
import renderer, { act } from 'react-test-renderer';

describe('DisplayData', () => {
  it('should show new entries when query is set', async () => {
    const mProps = {
      query: 'pippo',
      onQueryChange: jest.fn()
    };
    const FAKE_HITS = [{ objectID: 1, url: 'haha.com', title: 'haha' }];
    const axiosGetSpy = jest.spyOn(axios, 'get').mockResolvedValueOnce({ data: { hits: FAKE_HITS } });
    let component;
    await act(async () => {
      component = renderer.create(<DisplayData {...mProps}></DisplayData>);
    });
    expect(axiosGetSpy).toBeCalledWith('http://hn.algolia.com/api/v1/search?query=pippo');
    expect(component.toJSON()).toMatchSnapshot();
    axiosGetSpy.mockRestore();
  });

  it('should not fetch data when query is empty string', async () => {
    const mProps = {
      query: '',
      onQueryChange: jest.fn()
    };
    const axiosGetSpy = jest.spyOn(axios, 'get');
    let component;
    await act(async () => {
      component = renderer.create(<DisplayData {...mProps}></DisplayData>);
    });
    expect(axiosGetSpy).not.toBeCalled();
    expect(component.toJSON()).toMatchSnapshot();
    axiosGetSpy.mockRestore();
  });
});

100% 覆盖率的单元测试结果:

Unit test result with 100% coverage:

 PASS  src/stackoverflow/56410688/index.spec.tsx
  DisplayData
    ✓ should show new entries when query is set (28ms)
    ✓ should not fetch data when query is empty string (5ms)

-----------|----------|----------|----------|----------|-------------------|
File       |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
-----------|----------|----------|----------|----------|-------------------|
All files  |      100 |      100 |      100 |      100 |                   |
 index.tsx |      100 |      100 |      100 |      100 |                   |
-----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   2 passed, 2 total
Time:        3.666s

index.spec.tsx.snap:

// Jest Snapshot v1, 

exports[`DisplayData should not fetch data when query is empty string 1`] = `<ul />`;

exports[`DisplayData should show new entries when query is set 1`] = `
<ul>
  <li>
    <a
      href="haha.com"
    >
      haha
    </a>
  </li>
</ul>
`;

依赖版本:

"jest": "^24.9.0",
"react-test-renderer": "^16.11.0",
"react": "^16.11.0",
"axios": "^0.19.0",

源代码:https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/56410688

这篇关于如何使用异步函数和内部的 setState 测试 useEffect的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆