Node.jsМодуль 7: Тестирование и деплой
Интеграционное тестирование
Supertest, тестирование API
Цель урока
В этом уроке ты научишься:
- Тестировать API endpoints
- Использовать Supertest
- Настраивать тестовую базу данных
Supertest
Установка
npm install supertest --save-devБазовое использование
const request = require('supertest');
const app = require('../app');
describe('GET /api/users', () => {
test('returns list of users', async () => {
const response = await request(app)
.get('/api/users')
.expect('Content-Type', /json/)
.expect(200);
expect(response.body).toBeInstanceOf(Array);
});
});Структура приложения для тестов
// app.js
const express = require('express');
const app = express();
app.use(express.json());
app.use('/api/users', require('./routes/users'));
module.exports = app;
// server.js
const app = require('./app');
app.listen(3000, () => {
console.log('Server running');
});Тестирование CRUD
const request = require('supertest');
const app = require('../app');
const mongoose = require('mongoose');
const User = require('../models/User');
describe('Users API', () => {
beforeAll(async () => {
await mongoose.connect(process.env.TEST_DATABASE_URL);
});
afterAll(async () => {
await mongoose.connection.close();
});
beforeEach(async () => {
await User.deleteMany({});
});
describe('GET /api/users', () => {
test('returns empty array when no users', async () => {
const res = await request(app)
.get('/api/users')
.expect(200);
expect(res.body.data).toEqual([]);
});
test('returns users', async () => {
await User.create({ name: 'Test', email: 'test@test.com' });
const res = await request(app)
.get('/api/users')
.expect(200);
expect(res.body.data).toHaveLength(1);
});
});
describe('POST /api/users', () => {
test('creates user', async () => {
const res = await request(app)
.post('/api/users')
.send({ name: 'Test', email: 'test@test.com' })
.expect(201);
expect(res.body.data.name).toBe('Test');
const user = await User.findById(res.body.data.id);
expect(user).toBeTruthy();
});
test('returns 400 for invalid data', async () => {
const res = await request(app)
.post('/api/users')
.send({ name: '' })
.expect(400);
expect(res.body.error).toBeDefined();
});
});
describe('PUT /api/users/:id', () => {
test('updates user', async () => {
const user = await User.create({ name: 'Old', email: 'test@test.com' });
const res = await request(app)
.put(`/api/users/${user._id}`)
.send({ name: 'New' })
.expect(200);
expect(res.body.data.name).toBe('New');
});
test('returns 404 for non-existent user', async () => {
const fakeId = new mongoose.Types.ObjectId();
await request(app)
.put(`/api/users/${fakeId}`)
.send({ name: 'New' })
.expect(404);
});
});
describe('DELETE /api/users/:id', () => {
test('deletes user', async () => {
const user = await User.create({ name: 'Test', email: 'test@test.com' });
await request(app)
.delete(`/api/users/${user._id}`)
.expect(204);
const deleted = await User.findById(user._id);
expect(deleted).toBeNull();
});
});
});Тестирование с аутентификацией
const jwt = require('jsonwebtoken');
describe('Protected routes', () => {
let token;
let user;
beforeEach(async () => {
user = await User.create({
name: 'Test',
email: 'test@test.com',
password: 'hashedpassword'
});
token = jwt.sign(
{ id: user._id },
process.env.JWT_SECRET
);
});
test('returns 401 without token', async () => {
await request(app)
.get('/api/profile')
.expect(401);
});
test('returns profile with valid token', async () => {
const res = await request(app)
.get('/api/profile')
.set('Authorization', `Bearer ${token}`)
.expect(200);
expect(res.body.email).toBe('test@test.com');
});
});Тестовая база данных
MongoDB In-Memory
npm install mongodb-memory-server --save-devconst { MongoMemoryServer } = require('mongodb-memory-server');
const mongoose = require('mongoose');
let mongoServer;
beforeAll(async () => {
mongoServer = await MongoMemoryServer.create();
await mongoose.connect(mongoServer.getUri());
});
afterAll(async () => {
await mongoose.disconnect();
await mongoServer.stop();
});Хелперы для тестов
// tests/helpers.js
const jwt = require('jsonwebtoken');
const User = require('../models/User');
async function createTestUser(data = {}) {
return User.create({
name: 'Test User',
email: `test-${Date.now()}@test.com`,
password: 'password123',
...data
});
}
function generateToken(user) {
return jwt.sign({ id: user._id }, process.env.JWT_SECRET);
}
async function authenticatedRequest(app, user) {
const token = generateToken(user);
return {
get: (url) => request(app).get(url).set('Authorization', `Bearer ${token}`),
post: (url) => request(app).post(url).set('Authorization', `Bearer ${token}`),
put: (url) => request(app).put(url).set('Authorization', `Bearer ${token}`),
delete: (url) => request(app).delete(url).set('Authorization', `Bearer ${token}`)
};
}
module.exports = { createTestUser, generateToken, authenticatedRequest };Практика
Задание: Тест регистрации
Задача: Напиши тест для endpoint регистрации.
Решение:
describe('POST /api/auth/register', () => {
test('registers new user', async () => {
const res = await request(app)
.post('/api/auth/register')
.send({
name: 'New User',
email: 'new@test.com',
password: 'password123'
})
.expect(201);
expect(res.body.token).toBeDefined();
expect(res.body.user.email).toBe('new@test.com');
});
test('returns 400 if email exists', async () => {
await User.create({ email: 'exists@test.com', password: 'pass' });
const res = await request(app)
.post('/api/auth/register')
.send({ email: 'exists@test.com', password: 'pass' })
.expect(400);
expect(res.body.error).toContain('exists');
});
});Проверь себя
- Чем интеграционные тесты отличаются от юнит?
- Как тестировать защищённые маршруты?
- Зачем нужна тестовая база данных?