JavaRush /Курсы /Модуль 3: React /Типизация тестов с TypeScript и работа с интерфейсами

Типизация тестов с TypeScript и работа с интерфейсами

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

Подготовка к типизированным тестам

В вашем проекте уже должен быть установлен TypeScript, а также настроен Jest. Если вы подключались к нашим предыдущим лекциям — вы уже готовы! Наслаждаемся этой идиллией и движемся дальше.

Если только подключились:

  1. Убедитесь, что TypeScript уже установлен:
    npm install typescript @types/jest -D
    
  2. Установите типы для React Testing Library:
       npm install @testing-library/react @testing-library/jest-dom @types/testing-library__jest-dom -D
    

Работа с интерфейсами для компонентов

Допустим, у нас есть простой компонент UserCard, который показывает информацию о пользователе:

Код компонента

import React from 'react';

interface UserCardProps {
  name: string;
  age: number;
  email?: string; // email необязательный
}

export const UserCard: React.FC<UserCardProps> = ({ name, age, email }) => {
  return (
    <div data-testid="user-card">
      <h1 data-testid="user-name">{name}</h1>
      <p data-testid="user-age">Age: {age}</p>
      {email && <p data-testid="user-email">Email: {email}</p>}
    </div>
  );
};

Обратите внимание, что у нас есть интерфейс UserCardProps, который описывает структуру пропсов компонента. Основная магия TypeScript начинается именно здесь.

Типизация тестов на основе интерфейсов

Давайте напишем тест для этого компонента.

Первичный тест

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

test('renders UserCard with all props', () => {
  render(<UserCard name="Alice" age={25} email="alice@example.com" />);

  // Проверяем текстовые значения
  expect(screen.getByTestId('user-name')).toHaveTextContent('Alice');
  expect(screen.getByTestId('user-age')).toHaveTextContent('Age: 25');
  expect(screen.getByTestId('user-email')).toHaveTextContent('Email: alice@example.com');
});

Объяснение типизации

В этом тесте мы передали объект пропсов прямо в компонент <UserCard />. TypeScript автоматически проверяет, что свойства name, age, и email соответствуют указанному интерфейсу UserCardProps. Если вы забудете передать обязательный name или передадите число вместо строки — TypeScript тут же предупредит вас.

Ошибка намеренно?

Попробуйте закомментировать проп name:

render(<UserCard age={25} email="alice@example.com" />);

TypeScript выдаст ошибку вроде:

Property 'name' is missing in type '{ age: number; email: string; }' but required in type 'UserCardProps'.

Или добавьте неверный тип:

render(<UserCard name={123} age={25} email="alice@example.com" />);
И получите:
Type 'number' is not assignable to type 'string'.

Работа с моками и тестовыми данными

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

Мок для тестов

const mockUser: UserCardProps = {
  name: 'Bob',
  age: 30,
  email: 'bob@example.com',
};

test('renders UserCard with mock data', () => {
  render(<UserCard {...mockUser} />);

  // Проверяем текстовые значения
  expect(screen.getByTestId('user-name')).toHaveTextContent('Bob');
  expect(screen.getByTestId('user-age')).toHaveTextContent('Age: 30');
  expect(screen.getByTestId('user-email')).toHaveTextContent('Email: bob@example.com');
});

Почему это важно?

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

Типизация React Testing Library

Многие методы React Testing Library, такие как render и screen.getByText, уже типизированы. Однако отличным дополнением будет использование генерации типов для тестируемых данных.

Пример с типизацией поиска

Если у вас есть несколько элементов с одинаковыми текстами, вы можете использовать queryByTestId с типизированным тестовым ID.

const userCard = screen.getByTestId<HTMLDivElement>('user-card');
// Теперь TypeScript знает, что userCard — это HTMLDivElement
expect(userCard).toBeInTheDocument();

Это особенно полезно, если тестируемый элемент имеет нестандартный тип, например <input>, <textarea>, и вы хотите использовать методы, специфичные только для них.

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

Давайте рассмотрим ещё один пример, в котором компонент изменяет состояние на основе пользовательского ввода. Допустим, у нас есть компонент, где пользователь может указать свой возраст:

import React, { useState } from 'react';

interface AgeInputProps {
  onAgeSubmit: (age: number) => void;
}

export const AgeInput: React.FC<AgeInputProps> = ({ onAgeSubmit }) => {
  const [age, setAge] = useState<string>('');

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setAge(e.target.value);
  };

  const handleSubmit = () => {
    onAgeSubmit(Number(age));
  };

  return (
    <div>
      <input
        data-testid="age-input"
        type="number"
        value={age}
        onChange={handleChange}
      />
      <button data-testid="submit-button" onClick={handleSubmit}>
        Submit
      </button>
    </div>
  );
};

Типизированный тест

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

test('submits age on button click', () => {
  const mockOnAgeSubmit = jest.fn(); // Мокаем функцию обратного вызова
  render(<AgeInput onAgeSubmit={mockOnAgeSubmit} />);

  const input = screen.getByTestId<HTMLInputElement>('age-input');
  const button = screen.getByTestId('submit-button');

  // Изменяем значение инпута
  fireEvent.change(input, { target: { value: '30' } });
  expect(input.value).toBe('30');

  // Нажимаем кнопку и проверяем вызов мока
  fireEvent.click(button);
  expect(mockOnAgeSubmit).toHaveBeenCalledWith(30); // Тип проверяется тут!
});

Чем полезна типизация тестирования?

  1. Повышение надёжности тестов: типизированные компоненты и моки уменьшают вероятность ошибок, особенно при рефакторинге.
  2. Лучшая читаемость кода: обратная связь от TypeScript помогает понять, какие данные передаются и тестируются.
  3. Быстрая разработка: IDE подсказывает, если что-то упущено или неверно использовано.
2
Задача
Модуль 3: React, 17 уровень, 3 лекция
Недоступна
Фиктивные данные и проверка пропсов
Фиктивные данные и проверка пропсов
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ