Express API testlash
O’n dars davomida har o’zgarishni curl bilan sinadingiz. Marshrutlar
soni o’ntadan oshganda bu usul buziladi: bitta joyni tuzatib,
boshqasini buzganingizni bilmay qolasiz. Hammasini qayta sinash uchun
yigirma buyruq yozish kerak.
Bu darsda o’sha yigirma buyruqni kodga aylantiramiz.
Ikki xil test
Node.js testlash darsida node:test bilan
funksiyalarni sinadingiz. Bu yerda vazifa boshqacha: HTTP marshrutini
sinash kerak.
| Nima sinaladi | Vosita | Misol |
|---|---|---|
| Funksiya | node:test + assert |
Task.create() to’g’ri obyekt qaytaradimi |
| HTTP marshruti | node:test + supertest |
GET /api/tasks 200 beradimi |
npm install --save-dev supertest
node:test Node ichiga qurilgan — uni o’rnatish shart emas.
Ilovani listen’dan ajratish
Birinchi qadam — kod tuzilishini o’zgartirish. Hozir app.js ikki ish
qiladi: ilovani sozlaydi va serverni ishga tushiradi. Test uchun faqat
birinchisi kerak.
app.js — faqat ilova:
const express = require("express");
const tasksRouter = require("./routes/tasks");
const app = express();
app.use(express.json());
app.use("/api/tasks", tasksRouter);
app.use((request, response) => {
response.status(404).json({ error: "Bunday manzil topilmadi" });
});
app.use((err, request, response, next) => {
console.error(err);
response.status(500).json({ error: "Serverda xato yuz berdi" });
});
module.exports = app;
server.js — ishga tushirish:
const app = require("./app");
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server ishga tushdi: http://localhost:${PORT}`);
});
{
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"test": "node --test --test-concurrency=1 \"test/**/*.test.js\""
}
}
Bu ajratish model qatlami darsidagi tamoyilning davomi: har fayl bitta ish qiladi.
Birinchi test
test/tasks.test.js:
const test = require("node:test");
const assert = require("node:assert");
const request = require("supertest");
const app = require("../app");
test("GET /api/tasks massiv qaytaradi", async () => {
const response = await request(app).get("/api/tasks").expect(200);
assert.ok(Array.isArray(response.body));
});
npm test
✔ GET /api/tasks massiv qaytaradi (28.9922ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
Uch qator kod curl http://localhost:3000/api/tasks ning o’rnini bosdi —
va u endi har safar o’zi bajariladi.
.expect(200) — supertest’ning qulayligi: kod mos kelmasa test darhol
yiqiladi. assert.ok esa javob mazmunini tekshiradi.
POST tanasi bilan
test("POST /api/tasks yangi vazifa yaratadi", async () => {
const response = await request(app)
.post("/api/tasks")
.send({ title: "Test vazifasi" })
.expect(201);
assert.strictEqual(response.body.title, "Test vazifasi");
assert.strictEqual(response.body.isDone, false);
assert.ok(response.body.id > 0);
});
.send() JSON tanasini yuboradi va Content-Type sarlavhasini o’zi
qo’yadi. curl dagi -H "Content-Type: application/json" -d '{...}'
yozuvi shu bitta chaqiruvga siqildi.
Uchta tekshiruv uchta savolga javob beradi: sarlavha saqlandimi, boshlang’ich
holat to’g’rimi, id berildimi.
Xato holatlarini sinash
Yaxshi test to’plami faqat “hammasi joyida” holatini emas, xato holatlarini ham qamraydi:
test("POST bo'sh title bilan 400 qaytaradi", async () => {
const response = await request(app)
.post("/api/tasks")
.send({ title: " " })
.expect(400);
assert.strictEqual(response.body.error, "title maydoni majburiy");
});
test("GET mavjud bo'lmagan id 404 beradi", async () => {
await request(app).get("/api/tasks/99999").expect(404);
});
Zanjirli test
Ba’zi tekshiruvlar bir necha so’rovni talab qiladi:
test("DELETE yaratilgan vazifani o'chiradi", async () => {
const created = await request(app)
.post("/api/tasks")
.send({ title: "O'chiriladigan" })
.expect(201);
await request(app).delete(`/api/tasks/${created.body.id}`).expect(204);
// O'chirilgandan keyin topilmasligi kerak
await request(app).get(`/api/tasks/${created.body.id}`).expect(404);
});
Test o’zi ishlatadigan ma’lumotni o’zi yaratdi. Bu muhim odat:
bazada ilgaridan id: 5 turibdi deb taxmin qilgan test bir kun
kelib yiqiladi.
npm test
✔ GET /api/tasks massiv qaytaradi (28.5761ms)
✔ POST /api/tasks yangi vazifa yaratadi (35.1642ms)
✔ POST bo'sh title bilan 400 qaytaradi (8.5091ms)
✔ GET mavjud bo'lmagan id 404 beradi (9.2911ms)
✔ DELETE yaratilgan vazifani o'chiradi (30.7206ms)
ℹ tests 5
ℹ pass 5
ℹ fail 0
Test yiqilganda
Ataylab xato tekshiruv yozib ko’ring:
test("POST javobida title to'g'ri", async () => {
const response = await request(app).post("/api/tasks").send({ title: "X" });
assert.strictEqual(response.body.title, "Boshqa nom");
});
✖ POST javobida title to'g'ri (53.388ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
'X' !== 'Boshqa nom'
actual: 'X',
expected: 'Boshqa nom',
operator: 'strictEqual'
Xabar aniq: nima kutilgan, nima kelgan, qaysi faylning qaysi qatorida. Test yiqilishi — yomon xabar emas, aniq xabar.
Alohida test bazasi
Hozircha testlar haqiqiy tasks.db ga yozyapti. Har npm test dan keyin
bazada sinov axloti to’planadi.
db/index.js ni muhit o’zgaruvchisiga bo’ysundiramiz:
const path = require("path");
const { DatabaseSync } = require("node:sqlite");
// Test paytida boshqa fayl ishlatiladi
const fileName = process.env.DB_FILE || "tasks.db";
const databasePath = path.join(__dirname, "..", fileName);
const database = new DatabaseSync(databasePath);
module.exports = database;
Test bazasini tayyorlash — migratsiya darsidagi skript ishlatiladi:
DB_FILE=test.db node migrate.js
Bajarildi: 001-create-tasks
Bajarildi: 002-add-priority
Bajarildi: 003-create-users
Jami yangi migratsiya: 3
{
"scripts": {
"test": "cross-env DB_FILE=test.db node --test --test-concurrency=1 \"test/**/*.test.js\""
}
}
Autentifikatsiya talab qiladigan marshrut
Kirish darsida requireAuth bilan
himoyalangan marshrutlarni qanday sinaymiz? Sessiya cookie’sini saqlab
qolish kerak.
supertest’da buning uchun agent bor:
const test = require("node:test");
const assert = require("node:assert");
const request = require("supertest");
const app = require("../app");
test("kirmagan foydalanuvchi 401 oladi", async () => {
await request(app).get("/api/tasks").expect(401);
});
test("kirgan foydalanuvchi ro'yxatni oladi", async () => {
// agent cookie'ni so'rovlar orasida saqlaydi
const agent = request.agent(app);
await agent
.post("/register")
.send({
email: `test${Date.now()}@example.com`,
password: "sinov-parol-123",
})
.expect(201);
const response = await agent.get("/api/tasks").expect(200);
assert.ok(Array.isArray(response.body));
});
✔ kirmagan foydalanuvchi 401 oladi (31.7686ms)
✔ kirgan foydalanuvchi ro'yxatni oladi (152.7924ms)
request(app) va request.agent(app) farqi bitta: birinchisi har
so’rovni toza boshlaydi, ikkinchisi cookie’ni eslab qoladi — xuddi
curl -c va curl -b kabi.
Email Date.now() bilan yasalgani ham ataylab: har yurishda yangi
foydalanuvchi yaratiladi va
ro’yxatdan o’tish darsidagi 409 javobi
chiqmaydi.
Ikki tuzoq
Testlar birinchi marta yozilganda ikki narsa ajablantiradi.
Nimani sinash kerak
Hamma narsani sinash shart emas. Foyda beradigan tartib shunday:
| Ustuvorlik | Nimani | Nega |
|---|---|---|
| 1 | Pul va huquq bilan bog’liq marshrutlar | Xato qimmatga tushadi |
| 2 | Validatsiya va xato javoblari | Refaktoringda jim yo’qoladi |
| 3 | CRUD asosiy yo’llari | Tez-tez o’zgaradi |
| 4 | Sozlama va statik sahifalar | Kamdan-kam buziladi |
Bitta marshrut uchun ikki test yozing: ishlaydigan yo’l va eng ehtimolli xato. Bu nisbat ko’p loyiha uchun yetarli.
Amaliy mashq
task-app loyihasida davom eting:
app.jsniserver.jsdan ajratib,module.exports = appqiling;supertestni o’rnatib,GET /api/tasksuchun birinchi testni yozing vanpm testbilan yurgizing;POSTtestini.send()bilan yozib,201va javob mazmunini tekshiring;- Bitta
assertni ataylab xato qiymat bilan yozib, yiqilish xabarini o’qing, so’ng tuzating; DB_FILEo’zgaruvchisini qo’shib, testlarni alohidatest.dbga yo’naltiring vaDB_FILE=test.db node migrate.jsbilan tayyorlang;request.agent(app)bilan ro’yxatdan o’tib, himoyalangan marshrutdan200olishingizni tasdiqlang.
4-qadamni o’tkazib yubormang: yiqilish xabarini o’qishni bilmasangiz, testlar foydadan ko’ra ko’proq bezovta qiladi.
Qisqacha xulosa
supertestExpress ilovasiga to’g’ridan-to’g’ri so’rov yuboradi —app.listen()chaqirilmaydi.- Ilova (
app.js) serverni ishga tushirishdan (server.js) ajratiladi vamodule.exports = appqilinadi. .expect(200)holat kodini,assertesa javob mazmunini tekshiradi.- Test o’zi ishlatadigan ma’lumotni o’zi yaratsin; xato holatlari
(
400,404,401) uchun test to’g’ri yo’ldagidan qimmatroq. request.agent(app)cookie’ni saqlaydi va himoyalangan marshrutlarni sinash imkonini beradi.node --testni naqsh bilan chaqiring — usiz begona fayllar test sifatida ishga tushadi.- Bitta SQLite faylida parallel testlar qulfga uchraydi;
--test-concurrency=1buni yechadi.
Keyingi darsda ishga tushirish (deploy) — ilovani
localhost dan haqiqiy serverga chiqarish.