Ответ
Мы использовали комбинацию разных уровней тестирования с Jest в качестве основного фреймворка.
1. Unit-тесты:
Тестировали отдельные функции, модули и классы, изолируя их от внешних зависимостей с помощью моков (jest.mock()).
// service/userService.test.js
const userService = require('./userService');
const User = require('../models/User');
jest.mock('../models/User'); // Мокаем модель Mongoose
describe('User Service', () => {
test('createUser should hash password', async () => {
User.mockImplementation(() => ({
save: jest.fn().mockResolvedValue({ _id: '123', email: 'test@mail.com' })
}));
const result = await userService.createUser('test@mail.com', 'plainPassword');
expect(result.email).toBe('test@mail.com');
expect(result.password).not.toBe('plainPassword'); // Проверяем, что пароль захэширован
});
});
2. Интеграционные тесты:
Проверяли взаимодействие нескольких модулей, например, сервиса с реальной (или in-memory) базой данных. Для этого поднимали тестовый экземпляр MongoDB с помощью mongodb-memory-server.
3. API (E2E) тесты: С помощью Supertest тестировали HTTP-эндпоинты нашего Express-приложения.
// tests/api/users.test.js
const request = require('supertest');
const app = require('../../app');
const { generateAuthToken } = require('../../utils/auth');
describe('GET /api/users', () => {
test('should return 401 without token', async () => {
const res = await request(app).get('/api/users');
expect(res.statusCode).toBe(401);
});
test('should return user list for admin', async () => {
const adminToken = generateAuthToken({ userId: '1', role: 'admin' });
const res = await request(app)
.get('/api/users')
.set('Authorization', `Bearer ${adminToken}`);
expect(res.statusCode).toBe(200);
expect(Array.isArray(res.body)).toBe(true);
});
});
Инфраструктура и инструменты:
- Husky + lint-staged: Запуск линтера (
ESLint) и юнит-тестов перед коммитом. - Jest coverage: Генерация отчета о покрытии кода тестами (
npm test -- --coverage). - GitHub Actions: Автоматический прогон всей тестовой suites при каждом пуше в PR. Деплой на production происходил только после успешного прохождения всех тестов.
Ответ 18+ 🔞
А, ну это про тесты, да? Слушай, тут история как у того мужика, который решил проверить, выдержит ли табуретка — сел, а она развалилась. Вот и мы так же подошли: покрыли всё, от мелких функций до целых API, чтобы потом не охуеть в продакшене.
1. Юнит-тесты — это как проверять каждый винтик в двигателе отдельно.
Каждую функцию, каждый модуль мы тестили изолированно, от всех внешних зависимостей отъебались с помощью моков (jest.mock()). Чисто, без лишнего шума.
// service/userService.test.js
const userService = require('./userService');
const User = require('../models/User');
jest.mock('../models/User'); // Модель Mongoose просто послали нахуй, подсунули заглушку
describe('User Service', () => {
test('createUser should hash password', async () => {
User.mockImplementation(() => ({
save: jest.fn().mockResolvedValue({ _id: '123', email: 'test@mail.com' })
}));
const result = await userService.createUser('test@mail.com', 'plainPassword');
expect(result.email).toBe('test@mail.com');
expect(result.password).not.toBe('plainPassword'); // Смотрим, чтобы пароль захэшировался, а не лежал голым текстом
});
});
2. Интеграционные тесты — собираем винтики обратно и смотрим, крутится ли двигатель.
Тут уже проверяли, как несколько модулей друг с другом работают. Например, сервис с реальной (ну или in-memory) базой. Для этого поднимали тестовый MongoDB через mongodb-memory-server — удобно, не надо заморачиваться с отдельным сервером.
3. API (E2E) тесты — это уже полная сборка: садимся в машину и пытаемся завестись. Тут Supertest в руки и начинаем долбить по эндпоинтам нашего Express-приложения, как будто это делает настоящий пользователь.
// tests/api/users.test.js
const request = require('supertest');
const app = require('../../app');
const { generateAuthToken } = require('../../utils/auth');
describe('GET /api/users', () => {
test('should return 401 without token', async () => {
const res = await request(app).get('/api/users');
expect(res.statusCode).toBe(401); // Без токена — иди нахуй, 401
});
test('should return user list for admin', async () => {
const adminToken = generateAuthToken({ userId: '1', role: 'admin' });
const res = await request(app)
.get('/api/users')
.set('Authorization', `Bearer ${adminToken}`);
expect(res.statusCode).toBe(200); // А с админским токеном — вот тебе список, получай
expect(Array.isArray(res.body)).toBe(true);
});
});
Инфраструктура и инструменты — чтобы не превратиться в распиздяев:
- Husky + lint-staged: Перед каждым коммитом автоматом гоняли линтер (
ESLint) и юнит-тесты. Хуйню закоммитить не давали. - Jest coverage: Генерировали отчёт, сколько кода покрыто тестами (
npm test -- --coverage). Показатели были, конечно, не айс, но стремились. - GitHub Actions: Автоматический прогон ВСЕХ тестов при каждом пуше в PR. И самое главное — деплой на продакшен летел только если все тесты проходили. А иначе — сиди, чини, пока не заработает.