JavaRush /Курсы /Модуль 3: React /Сложные случаи в тестировании компонентов

Сложные случаи в тестировании компонентов

Модуль 3: React
17 уровень , 9 лекция
Открыта

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

1. Тестирование компонентов с контекстом (React Context)

Контекст в React — отличный способ передавать состояние между компонентами без пропсов. Но когда дело доходит до тестирования, возникает вопрос: "Как протестировать компоненты, которые зависят от контекста?"

Пример сложного случая:

У нас есть глобальный контекст авторизации, который предоставляет информацию о пользователе и функцию logout. Как протестировать компонент, использующий этот контекст?

// AuthContext.tsx
import React, { createContext, useContext } from 'react';

interface AuthContextType {
  user: string | null;
  logout: () => void;
}

const AuthContext = createContext<AuthContextType | undefined>(undefined);

export const AuthProvider: React.FC = ({ children }) => {
  const [user, setUser] = React.useState<string | null>('JohnDoe');

  const logout = () => setUser(null);

  return (
    <AuthContext.Provider value={{ user, logout }}>
      {children}
    </AuthContext.Provider>
  );
};

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within an AuthProvider');
  }
  return context;
};

И сам компонент:

// UserProfile.tsx
import React from 'react';
import { useAuth } from './AuthContext';

export const UserProfile: React.FC = () => {
  const { user, logout } = useAuth();

  return (
    <div>
      <p>Welcome, {user || 'Guest'}!</p>
      {user && <button onClick={logout}>Logout</button>}
    </div>
  );
};

Тест:

Для тестирования контекста мы можем создать его провайдер с фиктивными данными прямо в наших тестах.

// UserProfile.test.tsx
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { UserProfile } from './UserProfile';
import { AuthContext } from './AuthContext';

test('renders UserProfile with user and handles logout', () => {
  const mockLogout = jest.fn();

  render(
    <AuthContext.Provider value={{ user: 'TestUser', logout: mockLogout }}>
      <UserProfile />
    </AuthContext.Provider>
  );

  // Проверяем рендер компонента
  expect(screen.getByText(/Welcome, TestUser/i)).toBeInTheDocument();
  const logoutButton = screen.getByRole('button', { name: /logout/i });
  expect(logoutButton).toBeInTheDocument();

  // Симулируем нажатие на кнопку "Logout"
  fireEvent.click(logoutButton);
  expect(mockLogout).toHaveBeenCalledTimes(1);
});

Главная мысль:

Для контекста создавайте мок-провайдеры с фиктивными данными. Это позволит изолировать тестируемый компонент и не зависеть от глобального состояния приложения.

2. Тестирование асинхронных операций с задержками

Асинхронные операции, такие как запросы к API, часто сопровождаются задержками. Проблема возникает, когда наши тесты начинают "опережать" реальное выполнение кода.

Пример сложного случая:

Компонент загружает данные при монтировании и отображает индикатор загрузки.

// AsyncComponent.tsx
import React, { useEffect, useState } from 'react';

export const AsyncComponent: React.FC = () => {
  const [data, setData] = useState<string | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchData = async () => {
      setLoading(true);
      await new Promise((resolve) => setTimeout(resolve, 1000)); // симуляция задержки
      setData('Fetched Data');
      setLoading(false);
    };
    fetchData();
  }, []);

  if (loading) {
    return <p>Loading...</p>;
  }

  return <p>{data}</p>;
};

Тест:

Для таких случаев используйте waitFor из React Testing Library.

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

test('displays loading indicator and then fetched data', async () => {
  render(<AsyncComponent />);

  // Проверяем отображение индикатора загрузки
  expect(screen.getByText(/loading/i)).toBeInTheDocument();

  // Ждём, пока текст "Fetched Data" появится в DOM
  await waitFor(() =>expect(screen.getByText(/Fetched Data/i)).toBeInTheDocument());
});

Главная мысль:

Асинхронные операции требуют waitFor или findBy для проверки элементов, которые появляются позже.

3. Тестирование сложных пользовательских взаимодействий

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

Пример сложного случая:

Компонент открывает модальное окно по клику.

// ModalComponent.tsx
import React, { useState } from 'react';

export const ModalComponent: React.FC = () => {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div>
      <button onClick={() => setIsOpen(true)}>Open Modal</button>
        {isOpen && (
        <div role="dialog">
          <p>Modal Content</p>
          <button onClick={() => setIsOpen(false)}>Close</button>
        </div>
      )}
    </div>
  );
};

Тест:

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

test('opens and closes modal', () => {
  render(<ModalComponent />);

  // Проверяем, что модальное окно изначально скрыто
  expect(screen.queryByRole('dialog')).not.toBeInTheDocument();

  // Открываем модальное окно
  fireEvent.click(screen.getByText(/open modal/i));
  expect(screen.getByRole('dialog')).toBeInTheDocument();

  // Закрываем модальное окно
  fireEvent.click(screen.getByText(/close/i));
  expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
});

Главная мысль:

Для проверки сложных взаимодействий последовательно симулируйте действия пользователя и проверяйте состояние интерфейса на каждом шаге.

Типичные проблемы и как их избегать

  1. Избыточные тесты. Не нужно повторять тестирование одной и той же логики на всех уровнях. Например, протестировали API-запросы отдельно — не тестируйте их снова в рендер-тестах.

  2. Зависимость от внешних данных. Мокайте запросы к API. Используйте библиотеки вроде msw для более реалистичного тестирования.

  3. Сложные мок-данные. Всегда упрощайте моковые данные до минимально необходимого для теста. Чем проще фикстура — тем меньше вероятность ошибок.

  4. Игнорирование синхронности. Не забывайте использовать waitFor или findBy, когда работаете с асинхронными компонентами.

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

2
Задача
Модуль 3: React, 17 уровень, 9 лекция
Недоступна
Тестирование асинхронного компонента
Тестирование асинхронного компонента
3
Опрос
Создание моков и фикстур, 17 уровень, 9 лекция
Недоступен
Создание моков и фикстур
Создание моков и фикстур
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ