Тест: структура (Arrange, Act, Assert)
AAA (Arrange, Act, Assert) — паттерн структурирования тестов на три чёткие фазы: подготовка данных, выполнение действия, проверка результата.
Зачем нужно
Единая структура делает тесты читаемыми и предсказуемыми. Новый разработчик сразу понимает: что настраивалось, что вызывалось и что проверялось. Без AAA тесты превращаются в запутанный микс setup, действий и assertions.
Где используется
- Все виды тестов: unit, integration, E2E
- Jest, Vitest, Playwright, Cypress
- Эквивалент Given-When-Then в BDD-стиле
Основной контент
Базовая структура
test('описание сценария', () => {
// Arrange — подготовка
const price = 1000;
const discountPercent = 20;
// Act — действие
const result = applyDiscount(price, discountPercent);
// Assert — проверка
expect(result).toBe(800);
});
Более сложный пример
test('UserService.register создаёт пользователя и отправляет email', async () => {
// Arrange
const emailService = { send: jest.fn.mockResolvedValue(true) };
const userRepo = { save: jest.fn.mockResolvedValue({ id: 1, email: 'alice@test.com' }) };
const service = new UserService({ emailService, userRepo });
const input = { name: 'Alice', email: 'alice@test.com', password: 'Pass123!' };
// Act
const user = await service.register(input);
// Assert
expect(user.id).toBeDefined();
expect(userRepo.save).toHaveBeenCalledWith(expect.objectContaining({ name: 'Alice' }));
expect(emailService.send).toHaveBeenCalledWith({
to: 'alice@test.com',
subject: 'Добро пожаловать!',
});
});
AAA в BDD-стиле (Given-When-Then)
// Given → Arrange
// When → Act
// Then → Assert
describe('Корзина покупок', () => {
it('обновляет итог после добавления товара', () => {
// Given
const cart = new Cart();
const product = { id: 1, price: 500 };
// When
cart.add(product);
// Then
expect(cart.total).toBe(500);
});
});
AAA в E2E тестах
test('пользователь может войти', async ({ page }) => {
// Arrange
await page.goto('/login');
// Act
await page.fill('[data-testid="email"]', 'user@example.com');
await page.fill('[data-testid="password"]', 'secret');
await page.click('[data-testid="submit"]');
// Assert
await expect(page).toHaveURL('/dashboard');
await expect(page.getByTestId('welcome')).toBeVisible;
});
Один тест — одно утверждение
// ПЛОХО: смешано несколько аспектов
test('пользователь', () => {
const user = createUser('Alice');
expect(user.name).toBe('Alice');
expect(user.createdAt).toBeDefined();
expect(user.role).toBe('user');
// Если один expect падает — непонятно какой именно аспект сломан
});
// ХОРОШО: отдельные тесты с ясным намерением
test('имя устанавливается при создании', () => {
expect(createUser('Alice').name).toBe('Alice');
});
test('дата создания проставляется автоматически', () => {
expect(createUser('Alice').createdAt).toBeDefined();
});
Частые ошибки
- Отсутствие фаз — Act и Assert смешаны:
expect(service.call(data)).toBe(...)— трудно читать и отлаживать - Слишком много в Arrange — если setup занимает 30+ строк, вынеси его в
beforeEachили фабричную функцию - Несколько Act в одном тесте — проверяет несколько сценариев; раздели на отдельные тесты
- Assert до Act — логическая ошибка; всегда сначала выполни действие, потом проверяй результат