web-dev-qa-db-ja.com

内部で非同期関数とsetStateを使用してuseEffectをテストする方法

反応をテストする方法を理解するためにgithubプロジェクトをセットアップしました(v 16.8.0)useEffectフック。 useEffect内のデータをフェッチするAPI呼び出しを行い、受信したデータを状態コンポーネント要素として設定します。私のコンポーネントはクエリをプロップとして受け取り、クエリプロップ文字列が空でない場合にAPI呼び出しを行います。空でないクエリプロップを使用してapi呼び出しが行われ、コンポーネントがその状態を正しく設定することをテストしたいと思います。

テストuseEffectが直面する問題は、useEffectに関連する効果がブラウザの更新をブロックしないことです。 useEffectが機能する前に、テストが終了します。 Reactからドキュメントを読みますreact-test-utilsからactこれは、コンポーネントをレンダリングして更新を実行するコードをラップすると考えられています。使用しようとしても、コードで同じ問題が発生し続けます。

これは私がテストしようとしているコンポーネントです:

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が期待された2ではなくis_0_であるため、テストに失敗しました。

同じコンソールメッセージを挿入してアプリケーションをデバッグします。問題は、テストの実行後にuseEffectが起動されることですが、続行方法がわかりません。

[〜#〜] update [〜#〜] @jonrsharpeのおかげで、非同期バージョンがあるReact version 16.9.0-alpha.0を使用して問題を解決しましたactAPIの。

12
delca85

単体テストソリューションは次のとおりです。

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

5
slideshowp2