如何在内部使用异步功能和setState测试

时间:2019-06-01 21:50:59

标签: reactjs react-hooks jest react-testing-library

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

我知道测试 useEffect 面临的问题是与 useEffect 相关的效果不会阻止浏览器更新屏幕,因此测试进入了在 useEffect 发挥作用之前结束。 我从React文档中了解到,有一个来自 act-test-utils 的API,称为 act ,该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>
    );
};

这是我为此编写的测试:

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);
});

我不断收到警告,告诉我测试中DisplayData的更新未包含在act(...)中,并且由于 liCounts ,我的测试失败了收到的是_0_,而不是预期的 2

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

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

1 个答案:

答案 0 :(得分:3)

这是单元测试解决方案:

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%的单元测试结果:

 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