@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 +49 -0
- package/README.md +104 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/test.d.ts +5 -0
- package/dist/test.js +6 -0
- package/dist/testing-module.builder.d.ts +25 -0
- package/dist/testing-module.builder.js +102 -0
- package/dist/testing-module.d.ts +10 -0
- package/dist/testing-module.js +18 -0
- package/package.json +70 -0
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
|
+
[](https://www.npmjs.com/package/@velajs/testing)
|
|
4
|
+
[](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
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/test.d.ts
ADDED
package/dist/test.js
ADDED
|
@@ -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
|
+
}
|