Integration Test — database bilan testlash
Muammodan boshlaylik
Unit testlar o'tdi — funksiyalar to'g'ri ishlaydi. Lekin real loyihada bir muammo chiqdi: foydalanuvchi ro'yxatdan o'tyapti, DB ga yozilmayapti. Unit test hech narsani ko'rsatmadi — chunki DB mock qilingan edi.
Integration test — real DB bilan ishlaydigan test. "Kod va DB birga to'g'ri ishlaydimi?"
Integration test nima?
Unit test: bitta funksiya, DB yo'q. Integration test: bir nechta qism birga — DB ham ishtirok etadi.
Unit test: Integration test:
───────────── ─────────────────
funksiya HTTP so'rov
↓ ↓
mock DB real DB (test)
💡 Integration test sekinroq (DB bilan ishlaydi), lekin unit testda ko'rinmaydigan muammolarni topadi. Netflix, Stripe, GitHub — hammasi ikkalasini ishlatadi.
Test uchun alohida DB
Production DB ni testda ishlatmang — ma'lumotlar buziladi.
Eng oddiy yondashuv — SQLite (xotirada):
npm install --save-dev better-sqlite3
Yoki PostgreSQL bilan test uchun alohida DB:
DATABASE_URL=postgres://localhost/myapp_test
.env.test faylda:
DATABASE_URL=postgres://localhost/myapp_test
NODE_ENV=test
package.json:
{
"scripts": {
"test": "NODE_ENV=test jest"
}
}
Sequelize bilan misol
// db.js
import { Sequelize } from 'sequelize';
const isTest = process.env.NODE_ENV === 'test';
export const sequelize = new Sequelize(
isTest
? 'sqlite::memory:' // ← test — xotirada
: process.env.DATABASE_URL, // ← production — real DB
{ logging: false }
);
// models/User.js
import { DataTypes } from 'sequelize';
import { sequelize } from '../db.js';
export const User = sequelize.define('User', {
ism: { type: DataTypes.STRING, allowNull: false },
email: { type: DataTypes.STRING, unique: true },
parol: { type: DataTypes.STRING },
});
Integration test yozish
// tests/user.integration.test.js
import { sequelize } from '../db.js';
import { User } from '../models/User.js';
import { foydalanuvchiYarat } from '../services/userService.js';
// Har testdan oldin — jadval yaratish va tozalash
beforeEach(async () => {
await sequelize.sync({ force: true }); // ← jadvalni qayta yaratish
});
// Barcha testlar tugagach — ulanishni yopish
afterAll(async () => {
await sequelize.close();
});
describe('foydalanuvchiYarat', () => {
test('DB ga yozadi', async () => {
await foydalanuvchiYarat({
ism: 'Ali',
email: 'ali@test.com',
parol: 'Parol123',
});
const user = await User.findOne({ where: { email: 'ali@test.com' } });
expect(user).not.toBeNull();
expect(user.ism).toBe('Ali');
});
test('parolni hash qiladi', async () => {
await foydalanuvchiYarat({
ism: 'Ali',
email: 'ali@test.com',
parol: 'Parol123',
});
const user = await User.findOne({ where: { email: 'ali@test.com' } });
expect(user.parol).not.toBe('Parol123'); // ← ochiq parol saqlanmagan
});
test('bir email ikki marta — xato', async () => {
await foydalanuvchiYarat({ ism: 'Ali', email: 'ali@test.com', parol: 'Parol123' });
await expect(
foydalanuvchiYarat({ ism: 'Vali', email: 'ali@test.com', parol: 'Parol456' })
).rejects.toThrow('Bu email band');
});
});
Supertest + DB
HTTP endpointdan DB gacha to'liq zanjir:
// tests/auth.integration.test.js
import request from 'supertest';
import app from '../app.js';
import { sequelize } from '../db.js';
import { User } from '../models/User.js';
import bcrypt from 'bcrypt';
beforeEach(async () => {
await sequelize.sync({ force: true });
});
afterAll(async () => {
await sequelize.close();
});
describe('POST /auth/register', () => {
test('foydalanuvchi yaratadi', async () => {
const res = await request(app)
.post('/auth/register')
.send({ ism: 'Ali', email: 'ali@test.com', parol: 'Parol123' });
expect(res.status).toBe(201);
// DB da saqlanganini tekshirish
const user = await User.findOne({ where: { email: 'ali@test.com' } });
expect(user).not.toBeNull();
});
test('ikki marta ro\'yxatdan o\'tish — 409', async () => {
await request(app)
.post('/auth/register')
.send({ ism: 'Ali', email: 'ali@test.com', parol: 'Parol123' });
const res = await request(app)
.post('/auth/register')
.send({ ism: 'Ali', email: 'ali@test.com', parol: 'Parol123' });
expect(res.status).toBe(409);
});
});
describe('POST /auth/login', () => {
beforeEach(async () => {
// Test foydalanuvchisi
await User.create({
ism: 'Ali',
email: 'ali@test.com',
parol: await bcrypt.hash('Parol123', 10),
});
});
test('to\'g\'ri ma\'lumotlar — token', async () => {
const res = await request(app)
.post('/auth/login')
.send({ email: 'ali@test.com', parol: 'Parol123' });
expect(res.status).toBe(200);
expect(res.body.token).toBeDefined();
});
test('noto\'g\'ri parol — 401', async () => {
const res = await request(app)
.post('/auth/login')
.send({ email: 'ali@test.com', parol: 'xato' });
expect(res.status).toBe(401);
});
});
beforeAll va beforeEach farqi
// beforeAll — bir marta, barcha testlardan oldin
beforeAll(async () => {
await sequelize.sync({ force: true }); // ← bir marta yaratish
});
// beforeEach — har testdan oldin
beforeEach(async () => {
await User.destroy({ where: {} }); // ← har safar tozalash
});
// afterAll — bir marta, barcha testlardan keyin
afterAll(async () => {
await sequelize.close();
});
💡
force: truehamma jadvalni o'chirib qayta yaratadi. Tez, lekin katta loyihada sekin bo'lishi mumkin. O'sha holdatruncateishlatiladi — jadval tuzilmasi saqlanadi, faqat ma'lumotlar o'chadi.
Tez-tez uchraydigan xatolar
1. afterAll da DB ulanishini yopmaslik
Jest did not exit one second after the test run has completed.
// ✅
afterAll(async () => {
await sequelize.close(); // ← yoping
});
2. Testlar orasida ma'lumot qolishi
// ❌ — birinchi test yaratgan ma'lumot ikkinchiga ta'sir qiladi
test('birinchi', async () => {
await User.create({ email: 'test@test.com' });
});
test('ikkinchi', async () => {
const count = await User.count();
expect(count).toBe(0); // ← 1 bo'ladi!
});
// ✅ — beforeEach bilan tozalash
beforeEach(async () => {
await User.destroy({ where: {} });
});
3. Production DB ga ulanib qolish
// ✅ — environment tekshirilsin
if (process.env.NODE_ENV === 'production') {
throw new Error('Testni production da ishlatmang!');
}
Xulosa
- Integration test — real DB bilan, qismlar birga ishlaydi
- Test uchun alohida DB yoki SQLite xotirada — production ga tegmaydi
beforeEach— har testdan oldin ma'lumotlarni tozalangafterAll— testlar tugagach DB ulanishini yoping- Unit test tez, integration test ishonchli — ikkalasi kerak
Keyingi dars: 54-dars: Mock — funksiya, modul, database mock qilish