Загрузка файлов — распространённая задача в веб-приложениях. В 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:
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();
});
Иногда нужно тестировать не только отображение файлов, но и вызовы колбеков:
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, тестирование
похоже на обычный 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.Для загрузки файлов тест должен учитывать:
File с нужным содержимым и типом.fireEvent.change или
user-event.upload.dataTransfer.Такой подход позволяет покрыть все сценарии работы компонента с файлами и гарантировать, что функциональность загрузки работает корректно в разных условиях.