← Назад к вопросам

Как писал интеграционные тесты?

2.3 Middle🔥 131 комментариев
#Тестирование

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как писал интеграционные тесты?

Определение интеграционного теста

Интеграционный тест проверяет взаимодействие между несколькими компонентами/слоями системы: React компоненты + кастомные хуки + API запросы. В отличие от unit тестов, которые изолируют отдельные функции, интеграционные тесты проверяют реальные сценарии пользователя.

Мой подход: React Testing Library + Mock API

// === Тестирование компонента с API запросом ===
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
import UserProfile from '@/components/UserProfile';

// Мокируем API с MSW (Mock Service Worker)
const server = setupServer(
  http.get('/api/users/:id', ({ params }) => {
    return HttpResponse.json({
      id: params.id,
      name: 'John Doe',
      email: 'john@example.com',
    });
  })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

// Тест
it('displays user profile after loading', async () => {
  render(<UserProfile userId="123" />);
  
  // Проверяем, что идёт загрузка
  expect(screen.getByText(/loading/i)).toBeInTheDocument();
  
  // Ждём пока данные загрузятся
  await waitFor(() => {
    expect(screen.getByText('John Doe')).toBeInTheDocument();
  });
  
  expect(screen.getByText('john@example.com')).toBeInTheDocument();
});

// === Тестирование взаимодействия компонент + хук ===
import { useForm } from '@/hooks/useForm';

function LoginForm() {
  const { values, handleChange, handleSubmit, loading } = useForm({
    email: '',
    password: '',
  });

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="email"
        value={values.email}
        onChange={handleChange}
        placeholder="Email"
      />
      <input
        name="password"
        type="password"
        value={values.password}
        onChange={handleChange}
        placeholder="Password"
      />
      <button type="submit" disabled={loading}>
        {loading ? 'Signing in...' : 'Sign in'}
      </button>
    </form>
  );
}

it('submits login form with correct data', async () => {
  const user = userEvent.setup();
  
  server.use(
    http.post('/api/login', async ({ request }) => {
      const body = await request.json();
      if (body.email === 'test@example.com' && body.password === 'pass123') {
        return HttpResponse.json({ token: 'abc123' });
      }
      return HttpResponse.json({ error: 'Invalid credentials' }, { status: 401 });
    })
  );

  render(<LoginForm />);

  await user.type(screen.getByPlaceholderText('Email'), 'test@example.com');
  await user.type(screen.getByPlaceholderText('Password'), 'pass123');
  
  await user.click(screen.getByRole('button', { name: /sign in/i }));

  await waitFor(() => {
    expect(screen.getByText(/signed in/i)).toBeInTheDocument();
  });
});

Интеграционные тесты с E2E (Playwright)

// === tests/e2e/user-profile.spec.ts ===
import { test, expect } from '@playwright/test';

test('User can view profile and edit information', async ({ page }) => {
  // Авторизуемся через dev endpoint (если есть)
  await page.post('/api/v1/dev/test-auth', {
    data: {
      userId: '123',
      role: 'user',
    },
  });

  // Переходим на страницу профиля
  await page.goto('/profile');

  // Проверяем, что данные загружены
  await expect(page.getByText('John Doe')).toBeVisible();

  // Кликаем кнопку редактирования
  await page.getByRole('button', { name: /edit/i }).click();

  // Редактируем поле
  const nameInput = page.getByLabel('Full Name');
  await nameInput.fill('Jane Doe');

  // Отправляем форму
  await page.getByRole('button', { name: /save/i }).click();

  // Проверяем успешное сохранение
  await expect(page.getByText('Changes saved')).toBeVisible();
  await expect(page.getByText('Jane Doe')).toBeVisible();
});

test('Handles API errors gracefully', async ({ page }) => {
  // Блокируем API запрос к /api/users
  await page.route('/api/users/**', route => route.abort());

  await page.goto('/profile');

  // Проверяем сообщение об ошибке
  await expect(page.getByText(/error loading profile/i)).toBeVisible();
});

Best Practices для интеграционных тестов

// OK: Тест отражает пользовательское поведение
it('user flow: search, filter, and view results', async () => {
  render(<SearchPage />);
  
  const searchInput = screen.getByPlaceholderText('Search');
  await userEvent.type(searchInput, 'javascript');
  
  const filterBtn = screen.getByRole('button', { name: /filter/i });
  await userEvent.click(filterBtn);
  
  await waitFor(() => {
    expect(screen.getByText(/results for javascript/i)).toBeInTheDocument();
  });
});

// NOT OK: Тест зависит от деталей реализации
it('sets state variable to loaded', () => {
  const { result } = renderHook(() => useUserData());
  expect(result.current.isLoaded).toBe(true);
});

// OK: Проверяем видимый результат, не состояние
it('displays user data after loading', async () => {
  render(<UserComponent />);
  await waitFor(() => {
    expect(screen.getByText('User Name')).toBeInTheDocument();
  });
});

// OK: Используем semantic queries
screen.getByRole('button', { name: /submit/i });
screen.getByLabelText('Email');
screen.getByText(/welcome/i);

// NOT OK: CSS селекторы хрупкие
screen.getByTestId('user-input');
container.querySelector('.form-input');

Структура папок для тестов

frontend/
|-- src/
|   |-- components/
|   |   `-- UserProfile.tsx
|   `-- hooks/
|       `-- useUser.ts
|-- tests/
|   |-- unit/
|   |   `-- hooks/
|   |       `-- useUser.test.ts
|   |-- integration/
|   |   `-- UserProfile.test.tsx
|   `-- e2e/
|       `-- user-flow.spec.ts

Запуск тестов

# Unit + Integration тесты (Vitest)
npm test

# E2E тесты (Playwright)
npm run test:e2e

# Coverage
npm run test:coverage

# Watch mode
npm test -- --watch

Советы по написанию

  1. Один сценарий на тест — если нужно проверить несколько шагов, это один большой тест
  2. Реалистичные данные — используйте test factory или реальные примеры
  3. Тестируйте edge cases — пустые результаты, ошибки, таймауты
  4. Не мокируйте всё подряд — мокируйте только внешние API, остальное оставляйте реальным
  5. Проверяйте доступностьgetByRole вместо getByTestId
Как писал интеграционные тесты? | PrepBro