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:

  1. app.js ni server.js dan ajratib, module.exports = app qiling;
  2. supertest ni o’rnatib, GET /api/tasks uchun birinchi testni yozing va npm test bilan yurgizing;
  3. POST testini .send() bilan yozib, 201 va javob mazmunini tekshiring;
  4. Bitta assert ni ataylab xato qiymat bilan yozib, yiqilish xabarini o’qing, so’ng tuzating;
  5. DB_FILE o’zgaruvchisini qo’shib, testlarni alohida test.db ga yo’naltiring va DB_FILE=test.db node migrate.js bilan tayyorlang;
  6. request.agent(app) bilan ro’yxatdan o’tib, himoyalangan marshrutdan 200 olishingizni tasdiqlang.

4-qadamni o’tkazib yubormang: yiqilish xabarini o’qishni bilmasangiz, testlar foydadan ko’ra ko’proq bezovta qiladi.

Qisqacha xulosa

  • supertest Express ilovasiga to’g’ridan-to’g’ri so’rov yuboradi — app.listen() chaqirilmaydi.
  • Ilova (app.js) serverni ishga tushirishdan (server.js) ajratiladi va module.exports = app qilinadi.
  • .expect(200) holat kodini, assert esa 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 --test ni naqsh bilan chaqiring — usiz begona fayllar test sifatida ishga tushadi.
  • Bitta SQLite faylida parallel testlar qulfga uchraydi; --test-concurrency=1 buni yechadi.

Keyingi darsda ishga tushirish (deploy) — ilovani localhost dan haqiqiy serverga chiqarish.

Bu dars foydali bo'ldimi?

marta ko'rildi