Supertest — HTTP endpoint larni testlash
Muammodan boshlaylik
Jest bilan funksiyalarni test qila boshladingiz. Yaxshi. Lekin Express da asosiy ish — HTTP so'rovlar. GET /users, POST /auth/login, DELETE /posts/:id.
Bularni qanday testlaysiz? Serverni ishga tushirib, Postman dan qo'lda yuborasizmi? Har o'zgarishda? Bu test emas — bu qiynoq.
Supertest — serverga haqiqiy HTTP so'rov yuborib, javobni tekshirish imkonini beradi. Server ishga tushirilmaydi — to'g'ridan-to'g'ri Express ilovasiga ulanadi.
Supertest nima?
Supertest — Express ilovasini testlash uchun kutubxona. request(app).get('/users') yozasiz — u Express ga HTTP so'rov yuboradi, javobni qaytaradi.
test kodi Supertest Express app
| | |
|-- get('/users') ->| |
| |-- HTTP so'rov ---->|
| |<-- javob ----------|
|<-- status, body--| |
| | |
Real server, real port — kerak emas. Supertest hammasini o'zi boshqaradi.
O'rnatish
npm install --save-dev supertest
Express ilovasini ajratish
Testda app.listen() chaqirilmasligi kerak — Supertest o'zi boshqaradi.
// app.js — faqat Express sozlash
import express from 'express';
const app = express();
app.use(express.json());
app.get('/salom', (req, res) => {
res.json({ xabar: 'Salom!' });
});
export default app;
// server.js — faqat ishga tushirish
import app from './app.js';
app.listen(3000, () => console.log('Server 3000 portda'));
// server.js ni import qilmang — app.js ni import qiling
import request from 'supertest';
import app from './app.js';
💡
app.jsvaserver.jsni ajratish — test uchun eng muhim qadam. Ko'pchilik bunga e'tibor bermaydi va testda port konflikti yuzaga keladi.
Birinchi test
// tests/app.test.js
import request from 'supertest';
import app from '../app.js';
test('GET /salom — 200 qaytaradi', async () => {
const res = await request(app).get('/salom');
expect(res.status).toBe(200);
expect(res.body.xabar).toBe('Salom!');
});
CRUD testlash
// app.js
const mahsulotlar = [
{ id: 1, nomi: 'Noutbuk', narxi: 5000000 },
{ id: 2, nomi: 'Telefon', narxi: 3000000 },
];
app.get('/mahsulotlar', (req, res) => {
res.json(mahsulotlar);
});
app.get('/mahsulotlar/:id', (req, res) => {
const m = mahsulotlar.find((x) => x.id === Number(req.params.id));
if (!m) return res.status(404).json({ xabar: 'Topilmadi' });
res.json(m);
});
app.post('/mahsulotlar', (req, res) => {
const yangi = { id: Date.now(), ...req.body };
mahsulotlar.push(yangi);
res.status(201).json(yangi);
});
// tests/mahsulotlar.test.js
import request from 'supertest';
import app from '../app.js';
describe('GET /mahsulotlar', () => {
test('barcha mahsulotlarni qaytaradi', async () => {
const res = await request(app).get('/mahsulotlar');
expect(res.status).toBe(200);
expect(Array.isArray(res.body)).toBe(true);
expect(res.body.length).toBeGreaterThan(0);
});
});
describe('GET /mahsulotlar/:id', () => {
test('mavjud mahsulotni qaytaradi', async () => {
const res = await request(app).get('/mahsulotlar/1');
expect(res.status).toBe(200);
expect(res.body.nomi).toBe('Noutbuk');
});
test('yo\'q mahsulot — 404', async () => {
const res = await request(app).get('/mahsulotlar/9999');
expect(res.status).toBe(404);
expect(res.body.xabar).toBe('Topilmadi');
});
});
describe('POST /mahsulotlar', () => {
test('yangi mahsulot qo\'shadi', async () => {
const res = await request(app)
.post('/mahsulotlar')
.send({ nomi: 'Quloqchin', narxi: 500000 });
expect(res.status).toBe(201);
expect(res.body.nomi).toBe('Quloqchin');
expect(res.body.id).toBeDefined();
});
});
Header va token yuborish
Autentifikatsiya talab qiladigan endpointlarni testlash:
test('autentifikatsiyasiz — 401', async () => {
const res = await request(app).get('/profil');
expect(res.status).toBe(401);
});
test('token bilan — 200', async () => {
const token = jwt.sign({ id: 1 }, process.env.JWT_SECRET);
const res = await request(app)
.get('/profil')
.set('Authorization', `Bearer ${token}`); // ← header qo'shish
expect(res.status).toBe(200);
expect(res.body.id).toBe(1);
});
beforeEach / afterEach — har test oldida/keyin
describe('POST /auth/login', () => {
// Har test oldida DB ni tozalash
beforeEach(async () => {
await db.users.destroy({ where: {} });
await db.users.create({
email: 'ali@test.com',
password: await bcrypt.hash('Parol123', 10),
});
});
test('to\'g\'ri ma\'lumotlar — token qaytaradi', async () => {
const res = await request(app)
.post('/auth/login')
.send({ email: 'ali@test.com', password: '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', password: 'xato-parol' });
expect(res.status).toBe(401);
});
});
Tez-tez uchraydigan xatolar
1. app.listen() ni app.js da qoldirish
// ❌ — testda port konflikti bo'ladi
const app = express();
// ...
app.listen(3000); // ← app.js ichida
// ✅ — app.js va server.js ajratilgan
2. Test bir-birini buzishi
// ❌ — birinchi test holatni o'zgartiradi, ikkinchiga ta'sir qiladi
test('birinchi', async () => {
await request(app).post('/mahsulotlar').send({ nomi: 'Test' });
});
test('ikkinchi', async () => {
const res = await request(app).get('/mahsulotlar');
expect(res.body.length).toBe(2); // ← birinchi testga bog'liq!
});
// ✅ — beforeEach bilan holatni tiklash
3. Status kodini tekshirmaslik
// ❌ — 404 bo'lsa ham body ni tekshiradi
const res = await request(app).get('/mahsulotlar/999');
expect(res.body.nomi).toBe('Noutbuk'); // ← mantiqsiz
// ✅ — avval status, keyin body
expect(res.status).toBe(404);
Xulosa
- Supertest — server ishga tushirmasdan Express ga HTTP so'rov yuborish
app.js(sozlash) vaserver.js(ishga tushirish) — ajratish shart.get(),.post(),.send(),.set()— zanjir bilan qulay sintaksisbeforeEach— har test oldida holat tozalash- Har test: avval status kodi, keyin body
Keyingi dars: 52-dars: Unit Test — service va helper funksiyalarni testlash