Давайте представим следующий сценарий: вы пишете свой код React для большого предприятия, на которое работаете. Вы только что закончили реализовывать некоторую логику, она отлично работает, но перед слиянием вашего PR вам нужно запустить npm-run-test . Вы запускаете его, и некоторые тесты терпят неудачу. Чем вы сейчас занимаетесь? Удалить тесты? КОНЕЧНО НЕТ. Вам нужно понять их, чтобы знать, стоит ли вам изменить код, который вы написали, или тест (потому что в тот конкретный момент фича, над которой вы работали, не учитывалась).

Для этого вам нужно знать некоторые вещи о тестировании. В этой статье я предоставлю вам помощь, необходимую для преодоления этой ситуации.

Ваш первый набор тестов

Позвольте мне показать вам структуру набора тестов для этого простого компонента:

const Greet = ({ name }: GreetProps) => {
  return (
    <div>
      <h2>{name ? `Hello ${name}` : 'Hello'}</h2>
    </div>
  );
};

Набор тестов выглядит так

import { render, screen } from '@testing-library/react';
import Greet from './Greet';

describe('Greet', () => {
  test('Greet renders correctly', () => {
    render(<Greet />)
    const textElement = screen.getByRole("heading", {
      level: 2
    })
    expect(textElement).toBeInTheDocument();
  })

  test('Greet renders with a name', () => {
    render(<Greet name="John" />)
    const textElement = screen.getByText('Hello John')
    expect(textElement).toBeInTheDocument();
  })
})

Реагировать на тестовые запросы

Теперь вы видели в моем примере, что я использовал 2 функции для извлечения элементов DOM в своих тестах: getByRole и getByTests. Запросов React Test больше, и они представлены в порядке приоритета, который вы должны использовать:

  1. getByRole
  2. getByLabelText
  3. getByPlaceholderText
  4. получить по тексту
  5. getByDisplayElement
  6. getByAltText
  7. getByTitle
  8. getByTestId

Чтобы получить больше элементов в одном запросе, замените «get» на «getAll».

Покрытие кода

Чтобы получить отчет о покрытии кода, вам нужно добавить эту команду в ваш package.json:

“coverage”: “react-scripts test — coverage — watchAll — collectCoverageFrom=’src/components/**/*.{ts,tsx}’ — collectCoverageFrom=’!src/components/**/*.{types,stories,constants,test,spec}.{ts,tsx}’”

Для первого флага collectCoverageFrom я указываю путь к файлам, от которых я хочу получать отчет, а во втором от тех, от которых я не хочу (используя ! в начале). Теперь вам нужно запустить npm run coverage .

Если вы хотите иметь минимальный предел покрытия кода, вы можете добавить эту конфигурацию в ваш package.json:

  "jest": {
    "coverageThreshold": {
      "global": {
        "branches": 80,
        "functions": 80,
        "lines": 80,
        "statements": -10
      }
    }
  }

Jest потерпит неудачу, если покрытие кода для ветвей, функций и строк будет меньше 80%, а также если будет непроверено более 10 операторов.

Утверждения (код после expect())

Вы можете найти в моем предыдущем примере, что я использовал .toBeInTheDocument(). Это утверждение. Вы можете найти список совпадений, которые можно использовать с React: https://github.com/testing-library/jest-dom (testing-library установлена ​​в create-react-app, вы можете проверить в setupTests .ts)
и соответствия по умолчанию: https://jestjs.io/docs/expect#matchers

Что тестировать?

  • рендеринг компонента
  • компонент рендерится с реквизитом
  • компонент отображается в разных состояниях
  • компонент реагирует на события

найтиПо / найтиВсеПо

Так вы проверяете, отрисовывается ли компонент в конечном итоге за определенный период времени.

Предположим, что для рендеринга нашего компонента требуется 300 мс, мы должны использовать findByRole вместо getByRole и обрабатывать его асинхронность:

  test('Greet renders correctly', async () => {
    render(<Greet />)
    const textElement = await screen.findByRole("heading", {
      level: 2
    })
    expect(textElement).toBeInTheDocument();
  })

По умолчанию время ожидания составляет 1000 мс, но мы можем изменить это во втором параметре (называемом опциями), передав timeout: [no. мс].

Тестирование пользовательских хуков React

Предположим, у нас есть хук useCounter React:

import { useState } from 'react';

const useCounter = () => {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(prevCount => prevCount + 1);
  };

  const decrement = () => {
    setCount(prevCount => prevCount - 1);
  };

  return {
    count,
    increment,
    decrement
  };
};

export default useCounter;

мы можем написать тест, используя renderHook вместо рендера:

import { renderHook, act } from '@testing-library/react';
import useCounter from './useCounter';

describe('useCustomHook', () => {
  test('should update count when increment is called', () => {
    const { result } = renderHook(() => useCounter());

    act(() => {
      result.current.increment();
    });

    expect(result.current.count).toBe(1);
  });

  test('should update count when decrement is called', () => {
    const { result } = renderHook(() => useCounter());

    act(() => {
      result.current.decrement();
    });

    expect(result.current.count).toBe(-1);
  });
});

Тестовые HTTP-запросы

Если наш компонент получает запрос на сервер, мы можем локально проверить только функциональность этого компонента, не отправляя фактический запрос (только то, что он делает с этим ответом).

Наш компонент выглядит так:

import React, { useState, useEffect } from 'react';

const RaccoonComponent = () => {
  const [raccoonNames, setRaccoonNames] = useState([]);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch('https://my-domain/api/v0/racoons');
        if (!response.ok) {
          throw new Error('Request failed');
        }
        const data = await response.json();
        setRaccoonNames(data);
      } catch (error) {
        console.error('Error:', error);
      }
    };

    fetchData();
  }, []);

  return (
    <div>
      <h2>Raccoon Names</h2>
      <ul>
        {raccoonNames.map((name, index) => (
          <li key={index}>{name}</li>
        ))}
      </ul>
    </div>
  );
};

export default RaccoonComponent;

Для этого нам нужно установить dev-зависимость mock-service-worker: npm install msw — save-dev . Документы здесь, для более подробных примеров: https://mswjs.io/docs/getting-started/install.

Добавьте эти строки в setupTests.ts:

// src/setupTests.js
import { server } from "./mocks/server";
// 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());

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

И создайте фиктивную конечную точку: создайте папку mocks и там:

  • в server.ts:
// src/mocks/server.js
import { setupServer } from "msw/node";
import { handlers } from "./handlers";

// This configures a request mocking server with the given request handlers.
export const server = setupServer(...handlers);
  • в обработчиках.ts
import { rest } from "msw";

export const handlers = [
  rest.get("https://my-domain/api/v0/racoons", (req, res, ctx) => {
    return res(
      ctx.status(200),
      ctx.json([
        {
          name: "Trashy McGarbageface",
        },
        {
          name: "Rocky Cheeks",
        },
        {
          name: "Bandit McSnacks",
        },
      ])
    );
  }),
];

Теперь мы наконец можем написать тест для компонента:

  test("renders a list of racoons", async () => {
    render(<RaccoonComponent/>);
    const racoons = await screen.findAllByRole("listitem");
    expect(racoons).toHaveLength(3);
  });

Если вы хотите глубже погрузиться в мир тестирования, вы можете проверить:

Интеграционное тестирование: унификация поведения компонентов

В то время как модульные тесты проверяют отдельные блоки кода, интеграционное тестирование фокусируется на проверке взаимодействия между несколькими модулями. Приложения React состоят из множества компонентов, которые работают вместе, чтобы обеспечить бесперебойную работу пользователей. Интеграционное тестирование гарантирует, что эти компоненты интегрируются и функционируют гармонично. Такие инструменты, как React Testing Library и Cypress, позволяют разработчикам имитировать взаимодействие с пользователем и проверять поведение компонентов в реалистичной среде. Интеграционное тестирование жизненно важно для выявления проблем, которые могут возникнуть из-за взаимодействия между компонентами.

Тестирование моментальных снимков: визуальная регрессия стала проще

Снимки предоставляют мощный способ обнаружения непреднамеренных изменений внешнего вида компонентов React. Моментальное тестирование фиксирует сериализованное представление выходных данных компонента и сохраняет его в качестве эталона. При последующих тестовых прогонах выходные данные сравниваются с сохраненным эталоном, чтобы убедиться в отсутствии непреднамеренных изменений. Такие инструменты, как функция тестирования моментальных снимков Jest, упрощают процесс, упрощая обнаружение визуальных регрессий и обеспечивая согласованность между итерациями.

Тестирование производительности: оптимизация взаимодействия с пользователем

Приложения React должны обеспечивать плавный и отзывчивый пользовательский интерфейс. Тестирование производительности измеряет и анализирует скорость, отзывчивость и масштабируемость приложения React в различных условиях. Такие инструменты, как Lighthouse и WebPageTest, позволяют разработчикам оценивать важные показатели производительности, такие как время загрузки, время взаимодействия и использование ресурсов. Выявляя узкие места в производительности и оптимизируя производительность приложения, разработчики могут улучшить взаимодействие с пользователем и предотвратить неудовлетворенность пользователей, вызванную медленными или неотзывчивыми интерфейсами.

Сквозное тестирование: моделирование реальных сценариев

Сквозное (E2E) тестирование имитирует реальные сценарии путем тестирования приложения в целом, включая его интеграцию с внешними зависимостями, такими как API и базы данных. Инструменты тестирования E2E, такие как Cypress и Puppeteer, облегчают автоматизированное тестирование браузера, позволяя разработчикам моделировать потоки и взаимодействия пользователей. E2E-тестирование необходимо для выявления проблем, которые могут возникнуть из-за сложного взаимодействия между различными компонентами, чтобы гарантировать, что приложение работает так, как ожидается, в реальных условиях.

Я знаю, что это была длинная статья, но я надеюсь, что дал обзор предмета тестирования, особенно модульного тестирования, потому что это самый простой тип тестирования, и он встречается почти везде.

Увидимся в следующем!

Расширьте свое цифровое присутствие с помощью React: давайте вместе превратим ваше видение в увлекательные веб-приложения, которые очаровывают, преображают и оставляют неизгладимое впечатление. Свяжитесь с нами сегодня, и давайте вместе построим что-то необычное!