@velajs/testing 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,49 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0 (2026-04-30)
4
+
5
+ Bugs fixed, rebuilt on `@velajs/vela/internal`, no more `reflect-metadata` dep.
6
+
7
+ ### Fixes
8
+
9
+ - **Provider override shape was wrong.** `overrideProvider(token).useValue(v)` constructed `{ token, useValue }` but vela's container reads `{ provide, useValue }`. The misleading `as ProviderOptions` cast hid this; nothing actually overrode. Fixed.
10
+ - **`useClass` now uses the container's native `useClass` path** instead of the bogus `useFactory: () => new cls()` workaround. The previous comment claimed `Container.registerOptions` doesn't read `useClass` — it does (`vela/src/container/container.ts`).
11
+ - **`loader.resolveAllInstances()` is now awaited.** Was called synchronously even though vela's signature is async; lifecycle hooks fired before instance resolution finished.
12
+ - **APP_* token wiring uses vela's canonical `bindAppProviders` helper.** Replaces the five duplicated `useGlobalGuards/Pipes/Interceptors/Filters/Middleware(container.resolve(APP_*))` blocks that handled only the single-value case (vela switched to array form months ago). Now stays in lockstep with `VelaFactory.create` automatically.
13
+
14
+ ### Breaking changes
15
+
16
+ - **`@velajs/vela` peer bumped to `>=1.1.0`.** The `optional: true` flag is removed — the package is unusable without vela.
17
+ - **`reflect-metadata` dependency removed.** Drop `import 'reflect-metadata'` from your test setup files; vela's built-in polyfill is sufficient.
18
+ - **`prepublishOnly` switched from `bun run` to `pnpm run`** to match the rest of the workspace.
19
+
20
+ ### Internal
21
+
22
+ - **Rebuilt on `@velajs/vela/internal`** for framework primitives (`MetadataRegistry`, `Container`, `RouteManager`, `ModuleLoader`, `ComponentManager`, `VelaApplication`, `bindAppProviders`). Public types still come from `@velajs/vela`.
23
+ - **`bun.lock` deleted** — pnpm is the source of truth.
24
+
25
+ ### Migration
26
+
27
+ ```diff
28
+ - import 'reflect-metadata';
29
+ import { describe, it, expect } from 'vitest';
30
+ import { Test } from '@velajs/testing';
31
+
32
+ - @Module({
33
+ - providers: [{ token: SomeToken, useValue: 'x' }],
34
+ - })
35
+ + @Module({
36
+ + providers: [{ provide: SomeToken, useValue: 'x' }],
37
+ + })
38
+ class TestModule {}
39
+ ```
40
+
41
+ (The `token` → `provide` change applies to your `@Module()` providers, not just to overrides — vela's container has always read `provide`. The previous shape silently no-op'd.)
42
+
43
+ ## 0.1.0 (2026-02-20)
44
+
45
+ Initial release.
46
+
47
+ - `Test.createTestingModule()` builder.
48
+ - `overrideProvider/Guard/Pipe/Interceptor/Filter` with `useValue/useClass/useFactory`.
49
+ - `TestingModule` with `get()`, `createApplication()`, `close()`.
package/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # @velajs/testing
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@velajs/testing)](https://www.npmjs.com/package/@velajs/testing)
4
+ [![License: MIT](https://img.shields.io/npm/l/@velajs/testing)](https://github.com/velajs/testing/blob/main/LICENSE)
5
+
6
+ Test-module builder for [Vela](https://github.com/velajs/vela). Compose modules in isolation, override providers/guards/pipes/interceptors/filters, and exercise controllers via Hono's `app.request()` — without bootstrapping the real factory.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ pnpm add -D @velajs/testing
12
+ # Peer (already in your project): @velajs/vela ^1.1.0, hono ^4
13
+ ```
14
+
15
+ No `reflect-metadata` needed — Vela ships its own polyfill.
16
+
17
+ ## Quick Start
18
+
19
+ ```ts
20
+ import { describe, it, expect, beforeEach } from 'vitest';
21
+ import { Test } from '@velajs/testing';
22
+ import { MetadataRegistry } from '@velajs/vela';
23
+ import { Injectable, Module } from '@velajs/vela';
24
+
25
+ @Injectable()
26
+ class CatsService {
27
+ findAll() { return ['cat1', 'cat2']; }
28
+ }
29
+
30
+ @Module({ providers: [CatsService] })
31
+ class CatsModule {}
32
+
33
+ beforeEach(() => MetadataRegistry.clear());
34
+
35
+ describe('CatsService', () => {
36
+ it('returns cats', async () => {
37
+ const moduleRef = await Test.createTestingModule({
38
+ imports: [CatsModule],
39
+ }).compile();
40
+
41
+ expect(moduleRef.get(CatsService).findAll()).toEqual(['cat1', 'cat2']);
42
+ });
43
+ });
44
+ ```
45
+
46
+ ## Overriding providers
47
+
48
+ ```ts
49
+ const moduleRef = await Test.createTestingModule({ imports: [UsersModule] })
50
+ .overrideProvider(UsersService)
51
+ .useValue({ getUsers: () => ['mock-user'] })
52
+ .compile();
53
+ ```
54
+
55
+ `useValue`, `useClass`, and `useFactory` are all supported. Same builder for `overrideGuard`, `overridePipe`, `overrideInterceptor`, `overrideFilter`.
56
+
57
+ ```ts
58
+ .overrideGuard(AuthGuard).useValue({ canActivate: () => true })
59
+ .overrideInterceptor(LogInterceptor).useClass(NoopInterceptor)
60
+ .overrideProvider(CONFIG).useFactory({
61
+ factory: (env: EnvService) => ({ env: env.getEnv() }),
62
+ inject: [EnvService],
63
+ })
64
+ ```
65
+
66
+ Inline providers (skip importing a module):
67
+
68
+ ```ts
69
+ const moduleRef = await Test.createTestingModule({
70
+ providers: [InlineService],
71
+ }).compile();
72
+ ```
73
+
74
+ ## HTTP testing
75
+
76
+ ```ts
77
+ const moduleRef = await Test.createTestingModule({ imports: [ItemsModule] }).compile();
78
+ const app = await moduleRef.createApplication();
79
+
80
+ const res = await app.getHonoApp().request('/items');
81
+ expect(res.status).toBe(200);
82
+ expect(await res.json()).toEqual([{ id: 1, name: 'Item 1' }]);
83
+ ```
84
+
85
+ ## Lifecycle
86
+
87
+ `compile()` calls `onModuleInit` and `onApplicationBootstrap` automatically. `moduleRef.close()` triggers `onModuleDestroy` and `onApplicationShutdown`.
88
+
89
+ ## API
90
+
91
+ | Export | Purpose |
92
+ |---|---|
93
+ | `Test.createTestingModule(metadata)` | Returns a `TestingModuleBuilder`. |
94
+ | `TestingModuleBuilder` | Chain `overrideProvider/Guard/Pipe/Interceptor/Filter`, then `.compile()`. |
95
+ | `TestingModule` | Result of `compile()`: `get(token)`, `createApplication()`, `close()`. |
96
+ | `OverrideBy` | The fluent intermediate from `overrideX()` — exposed for type-narrowing. |
97
+
98
+ ## How it's wired
99
+
100
+ `@velajs/testing` consumes vela's framework primitives via `@velajs/vela/internal` (`MetadataRegistry`, `Container`, `RouteManager`, `ModuleLoader`, `ComponentManager`, `VelaApplication`, `bindAppProviders`). The same `bindAppProviders` that `VelaFactory.create` uses, so test-mode and run-mode app construction stay in lockstep automatically.
101
+
102
+ ## License
103
+
104
+ MIT
@@ -0,0 +1,3 @@
1
+ export { Test } from './test.js';
2
+ export { TestingModule } from './testing-module.js';
3
+ export { TestingModuleBuilder, OverrideBy } from './testing-module.builder.js';
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { Test } from "./test.js";
2
+ export { TestingModule } from "./testing-module.js";
3
+ export { TestingModuleBuilder, OverrideBy } from "./testing-module.builder.js";
package/dist/test.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import type { ModuleOptions } from '@velajs/vela';
2
+ import { TestingModuleBuilder } from './testing-module.builder.js';
3
+ export declare const Test: {
4
+ createTestingModule(metadata: ModuleOptions): TestingModuleBuilder;
5
+ };
package/dist/test.js ADDED
@@ -0,0 +1,6 @@
1
+ import { TestingModuleBuilder } from "./testing-module.builder.js";
2
+ export const Test = {
3
+ createTestingModule (metadata) {
4
+ return new TestingModuleBuilder(metadata);
5
+ }
6
+ };
@@ -0,0 +1,25 @@
1
+ import type { ModuleOptions, Token, Type } from '@velajs/vela';
2
+ import { TestingModule } from './testing-module.js';
3
+ export declare class OverrideBy {
4
+ private readonly builder;
5
+ private readonly token;
6
+ constructor(builder: TestingModuleBuilder, token: Token);
7
+ useValue(value: unknown): TestingModuleBuilder;
8
+ useClass(cls: Type): TestingModuleBuilder;
9
+ useFactory(options: {
10
+ factory: (...args: unknown[]) => unknown;
11
+ inject?: Token[];
12
+ }): TestingModuleBuilder;
13
+ }
14
+ export declare class TestingModuleBuilder {
15
+ private readonly metadata;
16
+ private overrides;
17
+ constructor(metadata: ModuleOptions);
18
+ overrideProvider(token: Token): OverrideBy;
19
+ overrideGuard(guard: Type): OverrideBy;
20
+ overridePipe(pipe: Type): OverrideBy;
21
+ overrideInterceptor(interceptor: Type): OverrideBy;
22
+ overrideFilter(filter: Type): OverrideBy;
23
+ private addOverride;
24
+ compile(): Promise<TestingModule>;
25
+ }
@@ -0,0 +1,102 @@
1
+ import { ComponentManager, Container, MetadataRegistry, ModuleLoader, RouteManager, VelaApplication, bindAppProviders } from "@velajs/vela/internal";
2
+ import { TestingModule } from "./testing-module.js";
3
+ export class OverrideBy {
4
+ builder;
5
+ token;
6
+ constructor(builder, token){
7
+ this.builder = builder;
8
+ this.token = token;
9
+ }
10
+ useValue(value) {
11
+ this.builder['addOverride']({
12
+ token: this.token,
13
+ provider: {
14
+ provide: this.token,
15
+ useValue: value
16
+ }
17
+ });
18
+ return this.builder;
19
+ }
20
+ useClass(cls) {
21
+ this.builder['addOverride']({
22
+ token: this.token,
23
+ provider: {
24
+ provide: this.token,
25
+ useClass: cls
26
+ }
27
+ });
28
+ return this.builder;
29
+ }
30
+ useFactory(options) {
31
+ this.builder['addOverride']({
32
+ token: this.token,
33
+ provider: {
34
+ provide: this.token,
35
+ useFactory: options.factory,
36
+ inject: options.inject
37
+ }
38
+ });
39
+ return this.builder;
40
+ }
41
+ }
42
+ export class TestingModuleBuilder {
43
+ metadata;
44
+ overrides = [];
45
+ constructor(metadata){
46
+ this.metadata = metadata;
47
+ }
48
+ overrideProvider(token) {
49
+ return new OverrideBy(this, token);
50
+ }
51
+ overrideGuard(guard) {
52
+ return new OverrideBy(this, guard);
53
+ }
54
+ overridePipe(pipe) {
55
+ return new OverrideBy(this, pipe);
56
+ }
57
+ overrideInterceptor(interceptor) {
58
+ return new OverrideBy(this, interceptor);
59
+ }
60
+ overrideFilter(filter) {
61
+ return new OverrideBy(this, filter);
62
+ }
63
+ addOverride(entry) {
64
+ const idx = this.overrides.findIndex((o)=>o.token === entry.token);
65
+ if (idx !== -1) {
66
+ this.overrides[idx] = entry;
67
+ } else {
68
+ this.overrides.push(entry);
69
+ }
70
+ }
71
+ async compile() {
72
+ let TestRootModule = class TestRootModule {
73
+ };
74
+ MetadataRegistry.setModuleOptions(TestRootModule, {
75
+ imports: this.metadata.imports,
76
+ providers: this.metadata.providers,
77
+ controllers: this.metadata.controllers,
78
+ exports: this.metadata.exports
79
+ });
80
+ const container = new Container();
81
+ container.register({
82
+ provide: Container,
83
+ useValue: container
84
+ });
85
+ // Register overrides BEFORE module loading so ModuleLoader skips originals.
86
+ for (const override of this.overrides){
87
+ container.register(override.provider);
88
+ }
89
+ const routeManager = new RouteManager(container);
90
+ ComponentManager.init(container);
91
+ const loader = new ModuleLoader(container, routeManager);
92
+ loader.load(TestRootModule);
93
+ bindAppProviders(routeManager, container, loader);
94
+ const app = new VelaApplication(container, routeManager);
95
+ const instances = await loader.resolveAllInstances();
96
+ app.setInstances(instances);
97
+ await app.callOnModuleInit();
98
+ await app.callOnApplicationBootstrap();
99
+ await app.initRoutes();
100
+ return new TestingModule(app, container);
101
+ }
102
+ }
@@ -0,0 +1,10 @@
1
+ import type { Token, VelaApplication } from '@velajs/vela';
2
+ import type { Container } from '@velajs/vela/internal';
3
+ export declare class TestingModule {
4
+ private readonly app;
5
+ private readonly container;
6
+ constructor(app: VelaApplication, container: Container);
7
+ get<T>(token: Token<T>): T;
8
+ createApplication(): Promise<VelaApplication>;
9
+ close(signal?: string): Promise<void>;
10
+ }
@@ -0,0 +1,18 @@
1
+ export class TestingModule {
2
+ app;
3
+ container;
4
+ constructor(app, container){
5
+ this.app = app;
6
+ this.container = container;
7
+ }
8
+ get(token) {
9
+ return this.container.resolve(token);
10
+ }
11
+ async createApplication() {
12
+ await this.app.initRoutes();
13
+ return this.app;
14
+ }
15
+ async close(signal) {
16
+ await this.app.close(signal);
17
+ }
18
+ }
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@velajs/testing",
3
+ "version": "0.2.0",
4
+ "description": "Testing utilities for Vela framework",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md",
17
+ "LICENSE",
18
+ "CHANGELOG.md"
19
+ ],
20
+ "scripts": {
21
+ "build": "rm -rf dist && swc src -d dist --strip-leading-paths && tsc --emitDeclarationOnly",
22
+ "test": "vitest run",
23
+ "typecheck": "tsc --noEmit",
24
+ "prepublishOnly": "pnpm run typecheck && pnpm test"
25
+ },
26
+ "sideEffects": false,
27
+ "keywords": [
28
+ "vela",
29
+ "testing",
30
+ "test",
31
+ "mock",
32
+ "override",
33
+ "edge",
34
+ "framework"
35
+ ],
36
+ "author": "ksh",
37
+ "license": "MIT",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/velajs/testing.git"
41
+ },
42
+ "homepage": "https://github.com/velajs/testing#readme",
43
+ "bugs": {
44
+ "url": "https://github.com/velajs/testing/issues"
45
+ },
46
+ "engines": {
47
+ "node": ">=20"
48
+ },
49
+ "peerDependencies": {
50
+ "@velajs/vela": ">=1.1.0",
51
+ "hono": ">=4"
52
+ },
53
+ "devDependencies": {
54
+ "@swc/cli": "^0.8.0",
55
+ "@swc/core": "^1.15.11",
56
+ "@types/bun": "latest",
57
+ "@velajs/vela": "^1.1.0",
58
+ "hono": "^4",
59
+ "typescript": "^5",
60
+ "unplugin-swc": "^1.5.9",
61
+ "vitest": "^4.0.18"
62
+ },
63
+ "packageManager": "pnpm@10.20.0",
64
+ "pnpm": {
65
+ "onlyBuiltDependencies": [
66
+ "@swc/core",
67
+ "esbuild"
68
+ ]
69
+ }
70
+ }