用 node:test 和 fetch 在一个临时端口上端到端地测试你的路由,每个测试都拥有自己的内存 SQLite 数据库。
在 Kodokon 中打开本课Node 20 自带了你需要的一切:node:test 作为运行器,node:assert/strict 用于断言,全局的 fetch 作为 HTTP 客户端。零测试依赖。而这正是第一课的架构开始回报的地方:createApp({ db }) 接受一个被注入的数据库,因此每个测试都能在几毫秒内挂载出一个完整而隔离的 API。
node --test
node --test --watch要端到端地测试路由,无需去 mock Express:在端口 0 上挂载一个真实的服务器(系统会分配一个空闲端口),然后用 fetch 去打它。你演练的正是真实客户端将会看到的一切:路由、中间件、序列化、状态码。这就是 supertest 的精神,却不带那个依赖。
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 };
}测试数据库:内存中的 SQLite(:memory:)。每个测试都通过 createDb 构建自己全新、空白的数据库(它会应用表结构),随后在退出时把它丢弃,测试之间没有共享数据,也没有清理脚本。t.after 保证即便某个断言失败,服务器也会被关闭。
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');
});app.listen(0) 来启动测试服务器?:memory: 数据库,决定性的优势是什么?t.after 里关闭服务器,而不是在测试主体的末尾?