@btravstack/testing

中文文档
0.58.0发布于 2026/11/08MITTypeScriptESM + CJS零依赖构建来源已验证近 30 天下载 8627

Test the applications @btravstack/core boots: an in-memory runtime, a fake clock, a boot fixture that stops what it started, and a tap into a running graph

原始使用文档
文档目录4 节
  1. A booted application, as a fixture
  2. What it ships
  3. A local JWT issuer, on its own subpath
  4. License

@btravstack/testing

Test the applications @btravstack/core boots. A test.extend fixture that starts a module with a test's defaults and stops every app it started when the test ends, a tap that hands back the very services a booted graph holds, an in-memory runtime, a clock that moves on demand — the way @nestjs/testing is to NestJS, and kept out of the kernel so a production bundle never pulls the fakes in.

📖 Documentation · Reference · API Reference

pnpm add -D @btravstack/testing @btravstack/core @btravstack/config @btravstack/di unthrown

@btravstack/core, @btravstack/config, @btravstack/di and unthrown are peer dependencies; the package depends on nothing else — not even vitest: bootFixture is a plain (ctx, use) => Promise<void> function, which is vitest's fixture protocol, so no import is needed to hand it to test.extend. jose is an optional peer behind the @btravstack/testing/jwt subpath — see below — so a consumer that never imports it installs nothing extra. Node >=22.

A booted application, as a fixture

import { bootFixture, tapped, type Boot } from "@btravstack/testing";
import { expect, test } from "vitest";

const it = test.extend<{ boot: Boot }>({
  boot: bootFixture({ env: { PORT: "0", HOST: "127.0.0.1" } }),
});

it("serves on an ephemeral port", async ({ boot }) => {
  const app = boot(OrderApi);

  await expect(app.runtimeInfo()).toBeOkWith(
    expect.objectContaining({ port: expect.any(Number) }),
  );
});

it("holds the very logger the use cases write to", async ({ boot }) => {
  const tap = tapped(OrderApi, [Logger]);
  await boot(tap.module).runtimeInfo();

  const [logger] = tap.services();
  expect(logger.lines()).toEqual([]);
});

boot is start as a test hands it out: the same signature, the same compile-time gate, minus signals — always off — with probes: false unless a call asks for a port, preDrainDelayMs: 0 and a silent onEvent as defaults, each overridable by the fixture's defaults and again per call. When the test ends every app it started is stopped, on every exit path, a failing assertion included; a Defect on exited fails the test even when the test never looked at it, while a modeled Err — a startup failure the test may be asserting — passes through.

tapped(module, [Port, …]) composes one more provider around module, depending on those ports, and remembers what it was built with: services() answers the very instances the running graph holds once it is built — after runtimeInfo() resolves, say — and throws before. The gate refuses a port module does not export.

What it ships

  • bootFixture(defaults?) — the fixture above; Boot and BootDefaults are its types.
  • tapped(module, ports) → { module, services() } — read services out of a booted application.
  • testRuntime(name?, { unit? }) / TestRuntimePort — an in-memory Runtime with submit() to hold a unit open across a drain, host() to reach the RuntimeHost it was last started with, and a module that provides it where a starter would; unit is a module every submitted unit forks, with no seed, before its work runs — and what that module needs joins module's own, so start names it instead of the first submit() failing.
  • overridden(module, [providers]) — substitute named providers into your real composition root, rather than maintaining a parallel test root beside it. It replaces one provider, never a subsystem: the replaced provider's siblings still construct, so swapping a whole adapter stack is still a different module composed in its place. An override for a port the tree stopped providing is a WiringDefect ("nothing to override"), which is what keeps a test root from drifting silently away from the real one — the drift that four hand-maintained parallel roots had already produced here. overridden(module, [providers], { unit: { user: [providers] } }) reaches inside a unit module the root binds, by kind, with the same drift gate at boot.
  • createFakeClock(start?) — a Clock for StartOptions.clock whose time moves only on advance(ms).

The teardown rule: a Defect on exited fails the test, a modeled Err does not.

A local JWT issuer, on its own subpath

import { localIssuer } from "@btravstack/testing/jwt";

const issuer = await localIssuer({
  issuer: "https://issuer.test",
  audience: "orders-api",
}).get();

const token = await issuer.sign({ sub: "u-1" }).get();
// verify `token` against `issuer.jwks`, the same way a real JWKS would be
await issuer.close();

sign accepts a string or readonly audience list, including deliberately broad tokens for recipient-policy rejection tests.

@btravstack/testing/jwt's localIssuer(options) mints a generated key pair, a node:http listener answering its public key as a JWKS, and a sign(claims?, options?) closing over the private key — a real fetch against a real JWKS document, for a test that wants the verifying library's own behaviour rather than a double's. sign's expiresIn: false mints a token with no exp claim, and the key is RS256. The subpath needs Node ≥22.12 under CommonJS — jose is ESM-only, so the CJS build's require("jose") depends on require(esm); ESM consumers, and anyone who never imports the subpath, are unaffected.

License

MIT © Benoit TRAVERS

版本与发布记录

@btravstack/testing 共发布 62 个版本,首次发布于 2026/08/26;过去 12 个月发布了 62 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.58.0latest2026/11/08今天
0.57.02026/11/071 天前
0.56.12026/11/062 天前
0.56.02026/11/062 天前
0.55.12026/11/035 天前
0.55.02026/11/035 天前

关于 @btravstack/testing 的常见问题

@btravstack/testing 是什么?

Test the applications @btravstack/core boots: an in-memory runtime, a fake clock, a boot fixture that stops what it started, and a tap into a running graph

如何安装 @btravstack/testing?

在项目目录运行 npm install @btravstack/testing,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:unthrown(^5.0.0)、@btravstack/di(^0.58.0)、@btravstack/core(^0.58.0)、@btravstack/config(^0.58.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@btravstack/testing 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@btravstack/testing 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@btravstack/testing 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@btravstack/testing 可以商用吗?

@btravstack/testing 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@btravstack/testing 的下载量有多少?

根据 npm 官方下载统计,@btravstack/testing 在 2026/10/09 – 2026/11/07 这 30 天内被下载 8,627 次,日均约 288 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@btravstack/testing 还在维护吗?

最新版本 0.58.0 发布于 2026/11/08(今天);至今共发布 62 个版本,其中过去 12 个月发布了 62 个;首次发布于 2026/08/26。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。