Загрузка файлов: upload файлов через input

Загрузка файлов — распространённая задача в веб-приложениях. В React Testing Library (RTL) тестирование элементов <input type="file"> требует моделирования поведения пользователя, чтобы проверить корректную обработку выбранных файлов компонентом.


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

Простейший компонент для загрузки файлов выглядит следующим образом:

import React, { useState } from 'react';

function FileUploader() {
  const [files, setFiles] = useState([]);

  const handleChange = (event) => {
    setFiles(Array.from(event.target.files));
  };

  return (
    <div>
      <input
        type="file"
        data-testid="file-input"
        multiple
        onCha nge={handleChange}
      />
      <ul>
        {files.map((file, index) => (
          <li key={index}>{file.name}</li>
        ))}
      </ul>
    </div>
  );
}

export default FileUploader;

Ключевые моменты:

  • Используется multiple для возможности выбора нескольких файлов.
  • Файлы сохраняются в состояние через Array.from(event.target.files).
  • data-testid позволяет удобно находить элемент в тесте.

Моделирование выбора файла в тестах

React Testing Library не оперирует реальными файлами на диске. Вместо этого создаются объекты File в памяти. Пример:

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

test('загружает один файл', () => {
  render(<FileUploader />);

  const fileInput = screen.getByTestId('file-input');
  const file = new File(['file content'], 'example.txt', { type: 'text/plain' });

  fireEvent.change(fileInput, { target: { files: [file] } });

  expect(screen.getByText('example.txt')).toBeInTheDocument();
});

test('загружает несколько файлов', () => {
  render(<FileUploader />);

  const fileInput = screen.getByTestId('file-input');
  const files = [
    new File(['first file'], 'first.txt', { type: 'text/plain' }),
    new File(['second file'], 'second.txt', { type: 'text/plain' }),
  ];

  fireEvent.change(fileInput, { target: { files } });

  expect(screen.getByText('first.txt')).toBeInTheDocument();
  expect(screen.getByText('second.txt')).toBeInTheDocument();
});

Особенности:

  • File создаётся через конструктор new File([content], name, options).
  • fireEvent.change моделирует выбор файлов пользователем.
  • Проверка через getByText позволяет убедиться, что компонент корректно обработал файлы.

Использование user-event для более реалистичных сценариев

fireEvent работает, но для более близкого к пользовательскому поведению моделирования рекомендуется user-event:

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import FileUploader from './FileUploader';

test('загружает файл через userEvent', async () => {
  render(<FileUploader />);
  const user = userEvent.setup();
  const fileInput = screen.getByTestId('file-input');
  const file = new File(['content'], 'userFile.txt', { type: 'text/plain' });

  await user.upload(fileInput, file);

  expect(screen.getByText('userFile.txt')).toBeInTheDocument();
});

Преимущества user-event.upload:

  • Более реалистичное воспроизведение действий пользователя.
  • Поддержка работы с несколькими файлами, drag-and-drop и другими интерактивными сценариями.
  • Позволяет писать асинхронные тесты без прямого использования fireEvent.

Проверка типов файлов и ошибок загрузки

В реальных приложениях часто проверяют типы файлов и ограничения по размеру. Тестирование таких сценариев включает создание файлов с различными свойствами:

test('отклоняет неподдерживаемый тип файла', async () => {
  render(<FileUploader />);
  const user = userEvent.setup();
  const fileInput = screen.getByTestId('file-input');
  const file = new File(['content'], 'image.png', { type: 'image/png' });

  await user.upload(fileInput, file);

  expect(screen.queryByText('image.png')).not.toBeInTheDocument();
});
  • В компоненте должна быть логика фильтрации файлов по типу, размеру или другим критериям.
  • Тест проверяет отсутствие неподдерживаемых файлов в DOM после попытки загрузки.

Проверка событий и колбеков

Иногда нужно тестировать не только отображение файлов, но и вызовы колбеков:

function FileUploaderWithCallback({ onFilesSelected }) {
  return (
    <input
      type="file"
      data-testid="file-input"
      multiple
      onCha nge={(e) => onFilesSelected(Array.from(e.target.files))}
    />
  );
}

test('вызывает колбек с загруженными файлами', async () => {
  const onFilesSelec ted = jest.fn();
  render(<FileUploaderWithCallback onFilesSelec ted={onFilesSelected} />);
  const fileInput = screen.getByTestId('file-input');
  const files = [new File(['data'], 'file.txt', { type: 'text/plain' })];

  fireEvent.change(fileInput, { target: { files } });

  expect(onFilesSelected).toHaveBeenCalledWith(files);
});
  • Используется jest.fn() для отслеживания вызова колбека.
  • Проверяется, что функция получила корректный массив файлов.

Особенности тестирования drag-and-drop загрузки

Для компонентов, которые поддерживают drag-and-drop, тестирование похоже на обычный input, но имитируются события dragEnter, drop:

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

test('обрабатывает drop файлов', () => {
  render(<FileDropzone />);
  const dropzone = screen.getByTestId('dropzone');
  const file = new File(['data'], 'drop.txt', { type: 'text/plain' });

  fireEvent.drop(dropzone, {
    dataTransfer: { files: [file] },
  });

  expect(screen.getByText('drop.txt')).toBeInTheDocument();
});
  • dataTransfer имитирует перенос файлов.
  • Важно, чтобы компонент корректно читал event.dataTransfer.files.

Итоговая структура теста

Для загрузки файлов тест должен учитывать:

  1. Создание объекта File с нужным содержимым и типом.
  2. Выбор метода — fireEvent.change или user-event.upload.
  3. Проверку отрисованного контента или вызова колбеков.
  4. Проверку ошибок или фильтрации файлов.
  5. В случае drag-and-drop — правильное использование dataTransfer.

Такой подход позволяет покрыть все сценарии работы компонента с файлами и гарантировать, что функциональность загрузки работает корректно в разных условиях.