如何在内部使用异步函数和setState测试useEffect

del*_*a85 9 reactjs jestjs react-testing-library react-hooks

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

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

这是我要测试的组件:

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>
    );
};
Run Code Online (Sandbox Code Playgroud)

这是我为此编写的测试:

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);
});
Run Code Online (Sandbox Code Playgroud)

我不断收到警告,告诉我

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

而且由于我的测试失败liCounts收到is_0_而不是预期的2

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

UPDATE 感谢@jonrsharpe我使用阵营版本16.9.0-alpha.0具有的异步版本解决了我的问题行为的API。

sli*_*wp2 13

这是单元测试解决方案:

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

我们使用act()帮助器来确保获取的数据已呈现并且 UI 已更新。

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

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

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>
  );
};
Run Code Online (Sandbox Code Playgroud)

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();
  });
});
Run Code Online (Sandbox Code Playgroud)

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

 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
Run Code Online (Sandbox Code Playgroud)

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>
`;
Run Code Online (Sandbox Code Playgroud)

依赖版本:

// 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>
`;
Run Code Online (Sandbox Code Playgroud)

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