Skip to content

写测试 ​

HestJS 不要求你装 jest 或 vitest。bun test 就是全部工具链。

ts
import { describe, expect, it } from 'bun:test';
import { createTestApp } from '@hestjs/testing';
import { AppModule } from './app.module';

const app = await createTestApp(AppModule, {
  routes: (hono, resolve) => hono.get('/users', (c) => resolve(Users).list(c)),
});

describe('用户接口', () => {
  it('健康检查', async () => {
    expect(await app.status('/health')).toBe(200);
  });

  it('创建用户', async () => {
    const created = await app.postJson<{ id: string }>('/users', { name: 'Ada' });
    expect(created.id).toBeDefined();
  });
});

createTestApp 是什么 ​

就是 createApp() 加一次 start(),再配一组请求便利方法:

方法作用
request(path, init?)原样发请求,返回 Response
json<T>(path, init?)解析 JSON
text(path, init?)取文本
status(path, init?)只取状态码
postJson<T>(path, body, init?)POST 一个 JSON body 并解析响应
close()逆序执行所有 OnStop

它不引入第二套运行时:app.app 是原来那个 App,app.hono 是原来那个 Hono 实例。所有便利方法最后都落到 hono.request()。

替换依赖 ​

ts
const app = await createTestApp(AppModule, {
  overrides: [
    { provide: Database, useValue: new FakeDatabase() },
    { provide: CLOCK, useValue: () => '2026-01-01T00:00:00Z' },
  ],
});

只能替换本来就注册过的 token。 名字写错会立刻抛 UnknownOverrideError:

测试替身 'typo' 没有对应的真实 provider。
overrides 只能替换「本来就注册过」的 token,不能凭空新增 ——
否则测试会通过,线上却少一个依赖。

这个约束是刻意的。框架不提供「运行时偷偷换实现」的后门,唯一入口是 createApp 的 overrides,而且它会校验。

不监听端口 ​

app.hono.request() 在内存里跑完整的中间件与路由链路, 不起服务器、不占端口。测试之间不会互相干扰,也不会因为端口冲突随机挂掉。

需要真实 HTTP(比如测 Bun.serve 的适配层)时才自己起服务:

ts
const server = Bun.serve({ port: 0, fetch: app.hono.fetch });

别忘了 close ​

ts
import { afterAll } from 'bun:test';

afterAll(() => app.close());

close() 会逆序执行所有 OnStop。持有连接池、定时器、worker 的 provider 靠它释放——不调的话 bun test 可能因为句柄没关而不退出。

测什么 ​

框架自己的测试可以看 packages/*/src/*.test.ts,它们是这么组织的:

层测法
容器与模块图直接 resolveModuleGraph() 断言结构,不起 HTTP
路由与中间件顺序createTestApp + status()
插件行为上两者混合,必要时读原型上的元数据表

@hestjs/openapi 的测试里还验证了「没有 @Describe 就不编造 summary」这类 不做事的保证——那些同样需要被测到。