Testování vlastního React Hooku pro načítání dat pomocí Axios

Hooks je nový koncept Reactu. Vyžaduje to určité přehodnocení stávajících znalostí. Navíc vývoj komponent React s háčky vyžaduje posun mysli (např. nemyslete v metodách životního cyklu). Potřebuje nějaký čas, aby se člověk dostal do pohody, ale s trochou praxe lze háčky bez problémů začlenit do skutečných projektů. Vlastní háky jsou velmi užitečné pro zapouzdření logiky do izolovaných modulů, které lze snadno znovu použít.

Testování háčků však (aktuálně) není snadný úkol. Napsání funkčních testů pro mé vlastní háčky mi trvalo docela dlouho. Tento příspěvek popisuje klíčové aspekty pro jejich testování.

Kód pro vlastní háček, stejně jako odpovídající testy, najdete v mém Code Sandbox.

Vlastní hák

Tento článek očekává, že budete vědět, jak napsat vlastní háky React. Pokud jste v tomto tématu noví, podívejte se na dokumentaci Reactu. Dalším dobrým výchozím bodem je podívat se na úžasné-react-háky.

Následující fragment kódu představuje jednoduchý vlastní háček pro provedení GET požádat pomocí axios .

// useFetch.js
import { useState, useEffect } from "react";
import axios from "axios";

// custom hook for performing GET request
const useFetch = (url, initialValue) => {
  const [data, setData] = useState(initialValue);
  const [loading, setLoading] = useState(true);
  useEffect(() => {
    const fetchData = async function() {
      try {
        setLoading(true);
        const response = await axios.get(url);
        if (response.status === 200) {
          setData(response.data);
        }
      } catch (error) {
        throw error;
      } finally {
        setLoading(false);
      }
    };
    fetchData();
  }, [url]);
  return { loading, data };
};

export default useFetch;

Následující kód ukazuje, jak lze tento vlastní háček použít.

import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";

import useFetch from "./useFetch";

function App() {
  const { loading, data } = useFetch(
    "https://jsonplaceholder.typicode.com/posts/"
  );

  return (
    <div className="App">
      {loading && <div className="loader" />}
      {data &&
        data.length > 0 &&
        data.map(blog => <p key={blog.id}>{blog.title}</p>)}
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Testování vlastního háku

V době psaní tohoto článku není testování háčků žádným přímočarým úkolem. Oficiální dokumentace Reactu poskytuje na toto téma pouze malou část. Měl jsem problém otestovat háčky kvůli porušení pravidel háčků.

Objevil jsem však knihovnu Reagovat-hooks-testing-library, která se zabývá spouštěním háčků v těle funkční komponenty a také poskytuje různé užitečné užitečné funkce.

Než napíšete své testy, musíte nainstalovat knihovnu spolu s jejími partnerskými závislostmi, jak je popsáno v dokumentaci:

$ npm i -D @testing-library/react-hooks
$ npm i react@^16.8.0
$ npm i -D react-test-renderer@^16.8.0

Vlastní hák využívá axios pro načítání dat. Potřebujeme způsob, jak zesměšnit skutečnou síť. Existuje mnoho způsobů, jak to udělat. Líbí se mi axios-mock-adapter, který usnadňuje psaní testů pro úspěšné a neúspěšné požadavky. Musíte také nainstalovat tyto knihovny.

$ npm i axios
$ npm i -D axios-mock-adapter

Nejprve se podívejte na následující Jest test, než probereme klíčové části.

// useFetch.test.js
import { renderHook } from "@testing-library/react-hooks";
import axios from "axios";
import MockAdapter from "axios-mock-adapter";

import useFetch from "./useFetch";

test("useFetch performs GET request", async () => {
  const initialValue = [];
  const mock = new MockAdapter(axios);

  const mockData = "response";
  const url = "http://mock";
  mock.onGet(url).reply(200, mockData);

  const { result, waitForNextUpdate } = renderHook(() =>
    useFetch(url, initialValue)
  );

  expect(result.current.data).toEqual([]);
  expect(result.current.loading).toBeTruthy();

  await waitForNextUpdate();

  expect(result.current.data).toEqual("response");
  expect(result.current.loading).toBeFalsy();
});

Implementace useFetch provede síťový požadavek pomocí axios . Proto zesměšňujeme GET než zavoláme useFetch .

// ...
const mock = new MockAdapter(axios);
// ...
/* 
  Mock network call. Instruct axios-mock-adapter 
  to return with expected data and status code of 200.
*/
mock.onGet(url).reply(200, mockData);
// invoke our custom hook
const { result, waitForNextUpdate } = renderHook(() =>
  useFetch(url, initialValue)
);

Jak vidíte, použijte Fetch je zabaleno do vyvolání funkce renderHook. Ve skutečnosti to znamená poskytnout správný kontext pro spuštění vlastního háku bez porušení pravidel háčků (v tomto případě lze háky volat pouze uvnitř těla komponenty funkce).

renderHook volání vrátí RenderHookResult. V našem příkladu destruujeme výsledek a waitForNextUpdate z výsledného objektu. Pojďme diskutovat o výsledku První.

// ...
const { result, waitForNextUpdate } = renderHook(() =>
  useFetch(url, initialValue)
);

expect(result.current.data).toEqual([]);
expect(result.current.loading).toBeTruthy();
// ...

výsledek tvoří výsledek renderHook. Jak můžete vidět v očekávání můžeme získat přístup ke skutečné návratové hodnotě našeho vlastního háku z result.current . Takže výsledek.aktuální.data a výsledek.aktuální.načítání podržte návratovou hodnotu volání vlastního zavěšení. Tato dvě tvrzení se hodnotí jako pravdivá. data stav obsahuje předanou počáteční hodnotu a načítání stav je pravdivý, protože skutečné síťové volání ještě nebylo provedeno.

Zatím je to dobré, ale jak provedeme hovor? Proto potřebujeme waitForNextUpdate .

// ...
const { result, waitForNextUpdate } = renderHook(() =>
  useFetch(url, initialValue)
);

expect(result.current.data).toEqual([]);
expect(result.current.loading).toBeTruthy();

await waitForNextUpdate();

expect(result.current.data).toEqual("response");
expect(result.current.loading).toBeFalsy();

waitForNextUpdate nám umožňuje čekat na návrat asynchronní funkce, abychom mohli zkontrolovat odpověď síťového volání.

Následující výpis je z dokumentace knihovny:

Po await waitForNextUpdate() vrátí, můžeme bezpečně tvrdit, že výsledek.aktuální.data obsahuje data pocházející z (zesměšněného) síťového požadavku. Navíc změna stavu voláním setLoading(false) bylo provedeno, a tedy výsledek.aktuální.načítání je nepravda .

Testování více případů použití

V následujícím textu uvidíte fragment kódu se dvěma dalšími testy. První testuje, zda naše implementace háku dokáže zpracovat více vyvolání. Druhý zkontroluje případ chyby sítě pomocí axios-mock-adapter .

test("useFetch performs multiple GET requests for different URLs", async () => {
  // fetch 1
  const initialValue = "initial value";
  const mock = new MockAdapter(axios);

  const mockData = 1;
  const url = "http://mock";
  mock.onGet(url).reply(200, mockData);

  const { result, waitForNextUpdate } = renderHook(() =>
    useFetch(url, initialValue)
  );

  expect(result.current.data).toEqual("initial value");
  expect(result.current.loading).toBeTruthy();

  await waitForNextUpdate();

  expect(result.current.data).toEqual(1);
  expect(result.current.loading).toBeFalsy();

  // fetch 2
  const url2 = "http://mock2";
  const mockData2 = 2;
  mock.onGet(url2).reply(200, mockData2);

  const initialValue2 = "initial value 2";
  const { result: result2, waitForNextUpdate: waitForNextUpdate2 } = renderHook(
    () => useFetch(url2, initialValue2)
  );

  expect(result2.current.data).toEqual("initial value 2");
  expect(result2.current.loading).toBeTruthy();

  await waitForNextUpdate2();

  expect(result2.current.data).toEqual(2);
  expect(result2.current.loading).toBeFalsy();
});

test("useFetch sets loading to false and 
returns inital value on network error", async () => {
  const mock = new MockAdapter(axios);

  const initialValue = [];
  const url = "http://mock";

  mock.onGet(url).networkError();

  const { result, waitForNextUpdate } = renderHook(() =>
    useFetch(url, initialValue)
  );

  expect(result.current.data).toEqual([]);
  expect(result.current.loading).toBeTruthy();

  await waitForNextUpdate();

  expect(result.current.loading).toBeFalsy();
  expect(result.current.data).toEqual([]);
});

Závěr

Opravdu se mi líbí API knihovny react-hooks-testing-library . Nejvíc se mi ale líbí, že mi knihovna umožňuje především testovat vlastní háčky. IMHO testování s touto knihovnou je jednoduché.

Pokud v konzole vidíte obtěžující varování, jak je znázorněno na následujícím snímku obrazovky, je vysoká pravděpodobnost, že to můžete opravit aktualizací svých závislostí.

Varování před zákonem bylo vyřešeno ve verzích Reag@^16.9.0 a @testing-library/react-hooks@^2.0.0.