Unit тесты
Unit-тест — автоматический тест одной изолированной единицы кода (функции, метода, класса) без реальных внешних зависимостей.
Зачем нужно
Unit-тесты — самый быстрый и дешёвый вид тестов: запускаются за секунды, дают мгновенную обратную связь. Изоляция от БД, сети и файловой системы гарантирует воспроизводимость и детерминированность результата. Хорошие unit-тесты служат живой документацией — они объясняют, что именно делает функция и какие граничные случаи предусмотрены.
Где используется
- Чистая бизнес-логика: расчёты, валидация, трансформации данных
- Утилитарные функции: форматирование дат, парсинг строк, вычисления
- Классы и сервисы с инжектируемыми зависимостями (mock вместо реального DB-клиента)
- При TDD — unit-тест пишется до реализации функции
- Фикс багов: тест воспроизводит баг, затем баг фиксируется
Основной контент
Структура unit-теста
// validators.js
function isEmail(str) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(str);
}
module.exports = { isEmail };
// validators.test.js
const { isEmail } = require('./validators');
describe('isEmail', () => {
// Позитивные случаи
test('принимает корректный email', () => {
expect(isEmail('user@example.com')).toBe(true);
});
test('принимает email с поддоменом', () => {
expect(isEmail('user@mail.example.com')).toBe(true);
});
// Негативные случаи
test('отклоняет строку без @', () => {
expect(isEmail('userexample.com')).toBe(false);
});
test('отклоняет пустую строку', () => {
expect(isEmail('')).toBe(false);
});
test('отклоняет null', () => {
expect(isEmail(null)).toBe(false);
});
});
Изоляция через mock-зависимости
// userService.js
class UserService {
constructor(db) {
this.db = db;
}
async getUserName(id) {
const user = await this.db.findById(id);
return user ? user.name.trim() : null;
}
}
module.exports = UserService;
// userService.test.js
const UserService = require('./userService');
describe('UserService.getUserName', () => {
test('возвращает имя с обрезанными пробелами', async () => {
// Arrange — mock вместо реальной БД
const mockDb = {
findById: jest.fn.mockResolvedValue({ name: ' Alice ' }),
};
const service = new UserService(mockDb);
// Act
const result = await service.getUserName(42);
// Assert
expect(result).toBe('Alice');
expect(mockDb.findById).toHaveBeenCalledWith(42);
});
test('возвращает null если пользователь не найден', async () => {
const mockDb = {
findById: jest.fn.mockResolvedValue(null),
};
const service = new UserService(mockDb);
const result = await service.getUserName(999);
expect(result).toBeNull();
});
});
Полезные матчеры Jest
expect(value).toBe(42); // строгое равенство ===
expect(obj).toEqual({ a: 1 }); // глубокое равенство
expect(arr).toHaveLength(3); // длина массива
expect(str).toContain('hello'); // подстрока
expect(fn).toThrow('error message'); // функция бросает ошибку
expect(mock).toHaveBeenCalledTimes(2);
expect(mock).toHaveBeenCalledWith('arg');
expect(value).toBeNull();
expect(value).toBeDefined();
expect(num).toBeGreaterThan(0);
Настройка и очистка
describe('Calculator', () => {
let calc;
beforeEach(() => {
calc = new Calculator(); // свежий экземпляр перед каждым тестом
});
afterEach(() => {
jest.clearAllMocks; // сброс mock-вызовов
});
test('складывает числа', () => {
expect(calc.add(2, 3)).toBe(5);
});
});
Частые ошибки
- Тестировать несколько вещей в одном тесте — один
test= одно утверждение; при падении сразу ясно что сломалось - Не изолировать зависимости — unit-тест, стучащийся в реальную БД или HTTP — это integration-тест, он медленный и нестабильный
- Хрупкие тесты на реализацию —
expect(fn.toString()).toContain('if')сломается при любом рефакторинге - Копипаста setup-кода — используй
beforeEachдля общей подготовки - Игнорировать async/await — если функция асинхронная, тест должен быть
asyncи возвращать промис