Как протестировать RTK Query с помощью библиотеки тестирования react

Тестирование компонентов с запросом в rtk-query с помощью msw и react-testing-library.

Привет всем, я начал тестировать веб-приложение react, и мои запросы на получение и загрузку данных выполняются с помощью rtk-query. Я расскажу вам, как писать тесты для компонентов при использовании rtk-запросов.

Сначала ознакомьтесь с моим руководством по настройке rtk query в redux toolkit.

Вы можете использовать библиотеку типа msw для имитации вашего api — именно это мы используем в кодовой базе Redux Toolkit для тестирования RTK Query.

phryneas (мейнтейнер инструментария Redux)

    npm install msw --save-dev
Вход в полноэкранный режим Выйти из полноэкранного режима

Чтобы протестировать RTK Query с помощью библиотеки тестирования react, нужно выполнить три шага,

  1. используйте msw для имитации вашего API.
  2. оберните ваш компонент в реальный магазин Redux с вашим API.
  3. напишите свои тесты — используйте что-то для ожидания изменений пользовательского интерфейса.

Настройка пользовательской функции рендеринга

Нам нужна пользовательская функция рендеринга, чтобы обернуть наши компоненты при тестировании. Эта функция называется renderWithProviders Узнать больше


// ./src/test-utils.js

import React from 'react'
import { render } from '@testing-library/react'
import { Provider } from 'react-redux'
import { setupStore } from './app/store'
import { setupListeners } from '@reduxjs/toolkit/dist/query'

export function renderWithProviders(
  ui,
  {
    preloadedState = {},
    // Automatically create a store instance if no store was passed in
    store = setupStore(preloadedState),
    ...renderOptions
  } = {}
) {

  setupListeners(store.dispatch);

  function Wrapper({ children }) {
    return <Provider store={store}>{children}</Provider>
  }

  return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) }
}

Вход в полноэкранный режим Выход из полноэкранного режима

Магазин Redux

Мы настроим наш redux store немного по-другому, подробнее об этом читайте здесь


// ./src/app/store.js


import { configureStore } from "@reduxjs/toolkit";
import { apiSlice } from "./api/apiSlice";



export const setupStore = preloadedState => {
  return configureStore({
    reducer: {
      [apiSlice.reducerPath]: apiSlice.reducer,
    },
    preloadedState,
    middleware: getDefaultMiddleware =>
        getDefaultMiddleware({
    immutableCheck: false,
    serializableCheck: false,
  }).concat(apiSlice.middleware),
  })
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Предоставление магазина приложению

Нам нужно обернуть наше react-приложение с настроенным нами redux store


// ./src/index.js

import { setupStore } from './app/store'
import { Provider } from 'react-redux';

const store = setupStore({});

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);
Войти в полноэкранный режим Выйти из полноэкранного режима

Настройка тестового окружения

Прежде чем мы начнем, мы должны настроить наше тестовое окружение на JEST

setupTests.js

// ./src/setupTests.js

import '@testing-library/jest-dom';
import { server } from './mocks/api/server'
import { apiSlice } from './app/api/apiSlice'
import { setupStore } from './app/store'

const store = setupStore({});


// Establish API mocking before all tests.
beforeAll(() => {
    server.listen();
});

// Reset any request handlers that we may add during the tests,
// so they don't affect other tests.
afterEach(() => {
    server.resetHandlers();
    // This is the solution to clear RTK Query cache after each test
    store.dispatch(apiSlice.util.resetApiState());
});

// Clean up after the tests are finished.
afterAll(() => server.close());

Войти в полноэкранный режим Выйти из полноэкранного режима

Мы сбрасываем API между тестами, поскольку API также имеет внутреннее состояние, вызывая store.dispatch(apiSlice.util.resetApiState()); после каждого теста.

Мокинг REST API

Мы используем msw для имитации (mock) запросов API, которые мы делаем в нашем приложении. Я покажу вам, как настроить и использовать msw.

msw

В каталоге src создайте папку mock и вложенную папку api.

Обработчик API

Обработчик содержит глобальную настройку для успешного запроса, если API был сымитирован (запрошен) успешно, ответ будет взят из того, что мы определили в объекте ответа msw.

./src/mock/api/handler.js


// ./src/mock/api/handler.js

import { rest } from 'msw'

export const handlers = [
  rest.get('https://jsonplaceholder.typicode.com/users', (req, res, ctx) => {

    // successful response
    return res(ctx.status(200), ctx.json([
        { id: 1, name: 'Xabi Alonzo' },
        { id: 2, name: 'Lionel Messi' },
        { id: 3, name: 'Lionel Love' },
        { id: 4, name: 'Lionel Poe' },
        { id: 5, name: 'Lionel Gink' },
    ]), ctx.delay(30))
  })
]
Вход в полноэкранный режим Выход из полноэкранного режима

./src/mock/api/server.js


// ./src/mock/api/server.js

import { setupServer } from 'msw/node'

import {handlers} from "./handler"

export const server = setupServer(...handlers)
Вход в полноэкранный режим Выход из полноэкранного режима

Наконец, пишем тесты

Тест 1: Выборка из API

Чтобы обработать запрос REST API, нам нужно указать его метод, путь и функцию, которая вернет насмешливый ответ. узнать больше.

Вот наша структура URL:

baseUrl: "https://api.coingecko.com/api/v3".

Параметры запроса: ?vs_currency=ngn&order=market_cap_desc&per_page=100&page=1.

Перехваченный запрос — это запрос, который мы делаем в нашем компоненте.

const queryRequest = {
  vs_currency: "usd",
  order: "market_cap_desc",
  per_page: "10",
  sparkline: "false",
  page
}

const {
  data: coins,
  isSuccess,
  isError,
  error,
  isLoading
} = useGetCoinsQuery(queryRequest)

getCoins: builder.query({
  query: (arg) => ({
    url: `/coins/markets`,
    params: {...arg}
  }),
  providesTags: ["coins"],
})
Вход в полноэкранный режим Выход из полноэкранного режима

Тест; получение данных из API


// ./src/__test__/Crypto.test.js

const apiData = [
  {name: "Mark Zuckerberg", age: "34"},
  {name: "Elon Musk", age: "44"}
]

test("table should render after fetching from API depending on request Query parameters", async () => {

    // custom msw server
    server.use(
      rest.get(`*`, (req, res, ctx) => {
          const arg = req.url.searchParams.getAll("page");
          console.log(arg)
          return res(ctx.json(apiData))         
        }
      ) 
      );


    // specify table as the render container
    const table = document.createElement('table')

    // wrap component with custom render function
    const { container } = renderWithProviders(<Coins />, {
      container: document.body.appendChild(table),
    });


    const allRows = await screen.findAllByRole("row")

    await waitFor(() => {
        expect(container).toBeInTheDocument();
    })  

    await waitFor(() => {
        expect(allRows.length).toBe(10);
    })
})
Вход в полноэкранный режим Выход из полноэкранного режима

объяснение теста

  1. создайте пользовательский сервер :- Для каждого теста мы можем переопределить обработчик API для тестирования отдельных сценариев, создав пользовательский сервер msw.
  2. apiData :- это ответ, который мы ожидаем получить от API.
  3. обернуть таблицу контейнером :- согласно документации RTL (react testing library), нам нужно указать table в качестве контейнера рендеринга.
  4. обернуть компонент :- мы обернем компонент, который хотим протестировать, нашей пользовательской функцией reder.
  5. подстановочный знак (*) :- мы используем его для представления api URL.
  6. get all tr element :- Я хочу получить все tr element, чтобы проверить, есть ли у нас до 10 строк в таблице. Для этого я использую row, подробнее можно узнать здесь

Тест 2: Подражание ответам на ошибки

Если вы хотите написать тест для сценария ошибки, например, когда сервер API недоступен.

Перехваченный запрос


{isError && (<p data-testid="error" className="text-center text-danger">Oh no, there was an error {JSON.stringify(error.error)} </p>)}

{isError && (<p data-testid="customError" className="text-center text-danger">{error.data.message}</p>)} 
Вход в полноэкранный режим Выход из полноэкранного режима

Тест; высмеивание ошибки sceneraio


// ./src/__test__/Crypto.test.js

test('renders error message if API fails on page load', async () => {
    server.use(
      rest.get('*', (_req, res, ctx) =>
        res.once(ctx.status(500), ctx.json({message: "baby, there was an error"}))
      )
    );

    renderWithProviders(<Coins />);

    const errorText = await screen.findByTestId("error");

    const errorMessage = await screen.findByTestId("customError");

    await waitFor(() => {
        expect(errorMessage.textContent).toBe("baby, there was an error")
    })

    await waitFor(() => {
        expect(errorText.textContent).toBe("Oh no, there was an error");
    })
});

Вход в полноэкранный режим Выход из полноэкранного режима

объяснение теста

  1. создание пользовательского сервера :- Для каждого теста мы можем переопределить обработчик API для тестирования отдельных сценариев, создав пользовательский сервер msw.
  2. Нам не нужен аргумент req argument, потому что мы тестируем на ошибки.
  3. обернуть компонент :- мы обернем компонент, который хотим протестировать, нашей пользовательской функцией reder.
  4. подстановочный знак (*) :- мы используем его для представления URL API.
  5. res status code :- мы намеренно выбрасываем ошибку с кодом состояния (500) для проверки на наличие ошибки.
  6. тело ответа :- мы передаем сообщение об ошибке как объект в тело ответа.

ссылка

Тестирование компонентов с помощью запроса rtk-query

Тестирование React-query с помощью MSW

Оцените статью
devanswers.ru
Добавить комментарий