Kodokon kodokon.com

การทดสอบ API ของคุณ: node:test, fetch และฐานข้อมูลสำหรับทดสอบ

ทดสอบ route ของคุณแบบ end to end ด้วย node:test และ fetch บนพอร์ตชั่วคราว โดยการทดสอบแต่ละครั้งได้ฐานข้อมูล SQLite ในหน่วยความจำเป็นของตัวเอง

9 นาที · 3 คำถาม

เปิดบทเรียนนี้ใน Kodokon

Node 20 มาพร้อมทุกสิ่งที่คุณต้องการ คือ node:test เป็นตัวรัน (runner), node:assert/strict สำหรับการยืนยัน (assertion) และ fetch ระดับโกลบอลเป็นไคลเอนต์ HTTP โดยไม่มีการพึ่งพาสำหรับทดสอบเลย และนี่คือจุดที่สถาปัตยกรรมจากบทแรกได้ผลตอบแทน createApp({ db }) รับฐานข้อมูลที่ถูกฉีดเข้ามา การทดสอบแต่ละครั้งจึงสามารถติดตั้ง API ที่สมบูรณ์และแยกจากกันได้ภายในไม่กี่มิลลิวินาที

BASH
node --test
node --test --watch
ตัวรันจะค้นหาไฟล์ *.test.js ของโปรเจกต์ได้ด้วยตัวเอง

ในการทดสอบ route แบบ end to end ไม่จำเป็นต้อง mock Express จงติดตั้งเซิร์ฟเวอร์จริงบน พอร์ต 0 โดยระบบจะกำหนดพอร์ตว่างให้ แล้วยิงคำขอไปที่มันด้วย fetch คุณจะได้ทดสอบสิ่งที่ไคลเอนต์จริงจะเห็นทุกอย่างพอดี คือ routing, middleware, การแปลงเป็นข้อมูลอนุกรม และรหัสสถานะ นี่คือจิตวิญญาณของ supertest โดยไม่มีการพึ่งพา

JAVASCRIPT
import { once } from 'node:events';

export async function startTestServer(app) {
  const server = app.listen(0);
  await once(server, 'listening');
  const { port } = server.address();
  const url = `http://127.0.0.1:${port}`;
  const close = () =>
    new Promise((resolve) => {
      server.close(resolve);
    });
  return { url, close };
}
helpers.js: เซิร์ฟเวอร์จริงบนพอร์ตที่ OS กำหนดให้

ฐานข้อมูลสำหรับทดสอบคือ SQLite ในหน่วยความจำ (:memory:) การทดสอบแต่ละครั้งสร้างฐานข้อมูลใหม่เปล่า ๆ ของตัวเองผ่าน createDb ซึ่งนำสคีมามาใช้ แล้วทิ้งมันไปตอนออก โดยไม่มีข้อมูลใช้ร่วมกันระหว่างการทดสอบ และไม่มีสคริปต์ล้างข้อมูล t.after รับประกันว่าเซิร์ฟเวอร์จะถูกปิด แม้ในตอนที่การยืนยันล้มเหลว

JAVASCRIPT
import test from 'node:test';
import assert from 'node:assert/strict';
import { createDb } from '../src/db/connection.js';
import { createApp } from '../src/app.js';
import { startTestServer } from './helpers.js';

test('POST /api/users creates a user', async (t) => {
  const db = createDb(':memory:');
  const app = createApp({ db });
  const server = await startTestServer(app);
  t.after(() => server.close());

  const res = await fetch(`${server.url}/api/users`, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({
      email: 'ada@example.com',
      name: 'Ada',
    }),
  });

  assert.equal(res.status, 201);
  const body = await res.json();
  assert.equal(body.email, 'ada@example.com');
});
การทดสอบแบบ end-to-end คือ HTTP stack จริง และฐานข้อมูลแบบใช้แล้วทิ้ง

ทดสอบความรู้

ตรวจสอบว่าคุณจำประเด็นสำคัญของบทเรียนนี้ได้ครบถ้วน

  1. ทำไมจึงเริ่มเซิร์ฟเวอร์ทดสอบด้วย app.listen(0)?
    • พอร์ต 0 ปิดการใช้งานเครือข่ายและเร่งการทดสอบ
    • ระบบจะกำหนดพอร์ตว่างให้ การทดสอบแบบขนานจึงไม่แย่งพอร์ตเดียวกัน
    • มันเป็นพอร์ตเดียวที่อนุญาตนอก production
    • พอร์ต 0 บังคับให้ Express เข้าสู่โหมดทดสอบ
  2. ข้อได้เปรียบชี้ขาดของฐานข้อมูล :memory: ที่สร้างขึ้นในแต่ละการทดสอบคืออะไร?
    • มันเก็บข้อมูลคงอยู่ระหว่างการรันเพื่อเปรียบเทียบ
    • การทดสอบแต่ละครั้งเริ่มจากสถานะว่างเปล่าที่แยกจากกัน โดยไม่มีสคริปต์ล้างข้อมูลและไม่มีการรบกวนกันระหว่างการทดสอบ
    • มันปิดข้อจำกัด SQL เพื่อทำให้การ insert ง่ายขึ้น
  3. ทำไมจึงปิดเซิร์ฟเวอร์ใน t.after แทนที่จะปิดตอนท้ายของตัวเนื้อการทดสอบ?
    • t.after ทำงานแม้การยืนยันจะล้มเหลว จึงเลี่ยงเซิร์ฟเวอร์กำพร้าที่ขวางไม่ให้โปรเซสจบการทำงาน
    • t.after ทำงานก่อนการทดสอบ ซึ่งเป็นการเตรียมเซิร์ฟเวอร์
    • มันเป็นเพียงเรื่องสไตล์ล้วน ๆ พฤติกรรมเหมือนกันทุกประการ