Тестирование пользовательских перехватчиков реакции, использующих выборку (или другое асинхронное поведение)
Пользовательский крючок
Есть несколько ошибок при тестировании пользовательских хуков с асинхронным поведением, таких как API выборки. На момент написания вы должны использовать альфа-версию (v16.9.0-alpha.0) react, react-dom и react-test-renderer. Надеюсь, что в недалеком будущем это станет частью стабильной кодовой базы React.
Обновление: версия 16.9.0 стала стабильной, не используйте альфа-версию, как упомянуто выше.
Если вы получаете такие ошибки, как…
Warning: An update to TestHook inside a test was not wrapped in act(...). When testing, code that causes React state updates should be wrapped into act(...): act(() => { /* fire events that update state */ }); /* assert on the output */ This ensures that you're testing the behavior the user would see in the browser. Learn more at https://fb.me/react-wrap-tests-with-act in TestHook in Suspense
… Это может помочь.
Вот простой пример написанного мной пользовательского хука, который вызывает API с использованием fetch
:
import { useEffect, useState, useRef } from "react"; export const useDataApi = () => { const firstUpdate = useRef(true); const [error, setError] = useState(false); const [apiUrl, setApiUrl] = useState(""); const [data, setData] = useState(null); useEffect( () => { if (!firstUpdate.current) { fetch(apiUrl) .then(response => response.json()) .then(response => { setData(response); }) .catch(error => { setError(true); }); } firstUpdate.current = false; }, [apiUrl] ); return { data, error, callApi: setApiUrl }; };
Я использую useRef(true)
, чтобы убедиться, что он не выполняет вызов при первом рендеринге, и useState
для обновления состояния ошибки / данных, которое затем возвращается для использования другими компонентами. useEffect
следит за тем, чтобы вызов API запускался только тогда, когда apiUrl
был обновлен через setApiUrl
.
Его можно импортировать и использовать в другом компоненте, вызвав callApi
со строкой, например:
... const { data, error, callApi } = useDataApi(); ... function handleClick(type, filterValue) { callApi("your/data/endpoint/here.com"); } ... return( ... {data.whateverIsReturned} ... )
Вы также можете использовать переменные error
для условного отображения сообщений об ошибках.
Тестирование
В моих примерах используется шутка, но вам понадобится еще несколько библиотек, чтобы заставить это работать.
react-test-renderer
(убедитесь, что это версия v16.9.0-alpha.0, пока не будет выпущена 16.9.0).fetch-mock
для перехвата моих сетевых запросов в целях тестирования. Если вы пользуетесь аксиомами, ознакомьтесь с шутками-макетами-аксиомами.@testing-library/react-hooks
предоставляет утилиты, упрощающие тестирование обработчиков.whatwg-fetch
позволяет нам использоватьfetch
в среде узла, как в браузере.
Boilerplate
Ваш тестовый файл без каких-либо тестов должен начинаться так:
import React from "react"; import { useDataApi } from "path/to/hoo/useDataApi.jsx"; import "whatwg-fetch"; import { renderHook } from "@testing-library/react-hooks"; import fetchMock from "fetch-mock"; import { act } from "react-test-renderer"; describe("useDataApi", () => { beforeAll(() => { global.fetch = fetch; }); afterAll(() => { fetchMock.restore(); }); });
В наших beforeAll
и afterAll
мы устанавливаем global.fetch
на полифилированную версию fetch, чтобы ее можно было использовать в ваших шутливых тестах, а затем мы восстанавливаем это после завершения тестов.
Тесты
Следует запомнить несколько вещей.
- Используйте
act
, чтобы обернуть любое поведение, которое обновляет состояние поведения, в нашем случае это вызываетcallApi
, но это также может быть, например, нажатие кнопки. - Поскольку выборка является асинхронной, мы должны использовать
await act(async () => {...})
Проверка успешного ответа
import React from "react"; import { useDataApi } from "path/to/hoo/useDataApi.jsx"; import "whatwg-fetch"; import { renderHook } from "@testing-library/react-hooks"; import fetchMock from "fetch-mock"; import { act } from "react-test-renderer"; describe("useDataApi", () => { beforeAll(() => { global.fetch = fetch; }); afterAll(() => { fetchMock.restore(); }); it("should return data with a successful request", async () => { const { result } = renderHook(() => useDataApi()); fetchMock.mock("test.com", { returnedData: "foo" }); await act(async () => { result.current.callApi("test.com"); }); expect(result.current.data).toBe({ returnedData: "foo" }); }); });
Проверка ошибочного ответа
import React from "react"; import { useDataApi } from "path/to/hoo/useDataApi.jsx"; import "whatwg-fetch"; import { renderHook } from "@testing-library/react-hooks"; import fetchMock from "fetch-mock"; import { act } from "react-test-renderer"; describe("useDataApi", () => { beforeAll(() => { global.fetch = fetch; }); afterAll(() => { fetchMock.restore(); }); it("should return error as true if api error", async () => { const { result } = renderHook(() => useDataApi()); fetchMock.mock("test.com", 500); await act(async () => { result.current.callApi("test.com"); }); expect(result.current.data).toBe(null); expect(result.current.error).toBe(true); }); });
Все Гисты можно найти здесь.