
你有沒有改過一行 code,然後整個 API 壞掉,但你是上線後才發現的?這篇教你用 Jest 寫測試,至少讓這種慘劇在本地就被抓到。接續 上一篇:ESLint + Typings 設定。
先講結論
- Jest +
ts-jest+supertest三件套,Express 路由測試就搞定 - 設定不難,但
jest.config.ts有幾個眉角(preset: 'ts-jest'別忘了) www.ts(伺服器啟動)要不要測?我的建議是:初期不用,把力氣花在路由邏輯上
安裝 Jest
pnpm add -D jest ts-jest @types/jest supertest @types/supertest然後初始化:
pnpm exec jest --init選項我建議這樣選:
| 問題 | 選擇 |
|---|---|
| Use Jest in “test” script? | Yes |
| Use TypeScript for config? | Yes |
| Test environment | node |
| Coverage reports? | Yes |
| Coverage provider | v8 |
| Auto clear mocks? | No |
設定 jest.config.ts
初始化完會生一個 jest.config.ts,改成這樣:
import type { Config } from 'jest';
const config: Config = {
clearMocks: true,
collectCoverage: true,
collectCoverageFrom: ['src/**/*.ts'],
coverageDirectory: "coverage",
coverageProvider: "v8",
preset: 'ts-jest',
testEnvironment: 'node',
};
export default config;preset: 'ts-jest' 是關鍵。沒加的話 Jest 看不懂 TypeScript,跑測試會直接噴 syntax error,你會困惑為什麼明明能編譯卻跑不了測試。問我怎麼知道的?別問。
寫測試
建立測試資料夾:
mkdir -p tests/routestests/routes/index.test.ts — 測首頁路由:
import request from 'supertest';
import app from '../../src/app';
describe('GET /', () => {
it('should return Welcome to the Home Page', async () => {
const res = await request(app).get('/');
expect(res.statusCode).toEqual(200);
expect(res.text).toContain('Welcome to the Home Page');
});
});tests/routes/users.test.ts — 測 users 路由:
import request from 'supertest';
import app from '../../src/app';
describe('GET /users', () => {
it('should return user resource', async () => {
const res = await request(app).get('/users');
expect(res.statusCode).toEqual(200);
expect(res.text).toContain('respond with a resource');
});
});注意這裡用的是 supertest,它可以直接把 Express app 丟進去,不用真的起一個 server。這代表你的測試跑起來很快,也不會有 port 衝突的問題。
跑測試
pnpm run test結果應該長這樣:
PASS tests/routes/index.test.ts
PASS tests/routes/users.test.ts
------------|---------|----------|---------|---------|
File | % Stmts | % Branch | % Funcs | % Lines |
------------|---------|----------|---------|---------|
All files | 33.58 | 66.66 | 0 | 33.58 |
app.ts | 100 | 100 | 100 | 100 |
www.ts | 0 | 0 | 0 | 0 |
routes/ | 100 | 100 | 100 | 100 |
------------|---------|----------|---------|---------|
Test Suites: 2 passed, 2 total
Tests: 2 passed, 2 total
www.ts 要不要測?
你會看到 coverage 報告裡 www.ts 是 0%。這正常嗎?
我覺得正常。www.ts 做的事情就是啟動 server、處理 port 錯誤。要測它的話你得 mock http.createServer、模擬 EADDRINUSE 錯誤… 搞半天測的都是 Node.js 本身的行為,不是你的商業邏輯。
如果你真的很在意 coverage 數字,可以測這三個場景:
- server 成功啟動
- port 被佔用(
EADDRINUSE) - 權限不足(
EACCES)
但我的建議是:先把路由和 middleware 的測試寫好,www.ts 的測試等專案長大了再說。測試也要講 CP 值,不是嗎?
最終資料夾結構
├── eslint.config.mjs
├── jest.config.ts
├── package.json
├── src/
│ ├── app.ts
│ ├── bin/www.ts
│ └── routes/
├── tests/
│ └── routes/
│ ├── index.test.ts
│ └── users.test.ts
└── typings/
└── routes/
到這裡,你的 Express + TypeScript 專案有了 linter、有了型別管理、有了測試。可以說是從「能跑的 prototype」變成了「可以認真開發的 project」了。
沒有測試的 code 就像沒有煞車的車 — 跑得很快,但你不敢轉彎。