@justscale/testing 0.1.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/LICENSE +21 -0
- package/README.md +44 -0
- package/dist/app.d.ts +69 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +105 -0
- package/dist/app.js.map +1 -0
- package/dist/client.d.ts +190 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +122 -0
- package/dist/client.js.map +1 -0
- package/dist/conformance/cluster-primitives.d.ts +75 -0
- package/dist/conformance/cluster-primitives.d.ts.map +1 -0
- package/dist/conformance/cluster-primitives.js +50 -0
- package/dist/conformance/cluster-primitives.js.map +1 -0
- package/dist/container.d.ts +70 -0
- package/dist/container.d.ts.map +1 -0
- package/dist/container.js +95 -0
- package/dist/container.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/kit.d.ts +82 -0
- package/dist/kit.d.ts.map +1 -0
- package/dist/kit.js +114 -0
- package/dist/kit.js.map +1 -0
- package/dist/mock.d.ts +137 -0
- package/dist/mock.d.ts.map +1 -0
- package/dist/mock.js +464 -0
- package/dist/mock.js.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter conformance suite - cluster primitives.
|
|
3
|
+
*
|
|
4
|
+
* Every cluster-capable adapter (Postgres, Redis, future) must pass this
|
|
5
|
+
* suite to claim conformance. Shape follows the well-trod JDBC/ODBC
|
|
6
|
+
* pattern: one abstract spec, multiple concrete runners.
|
|
7
|
+
*
|
|
8
|
+
* ## How to use
|
|
9
|
+
*
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { describeClusterConformance } from '@justscale/testing/conformance';
|
|
12
|
+
* import { PostgresFeature, PostgresChannelFeature, PostgresLockFeature,
|
|
13
|
+
* PostgresProcessFeature } from '@justscale/postgres';
|
|
14
|
+
*
|
|
15
|
+
* describeClusterConformance('postgres (real docker pg)', {
|
|
16
|
+
* makeInstance: () => JustScale()
|
|
17
|
+
* .add(env)
|
|
18
|
+
* .add(PostgresFeature)
|
|
19
|
+
* .add(PostgresChannelFeature)
|
|
20
|
+
* .add(PostgresLockFeature)
|
|
21
|
+
* .add(PostgresProcessFeature),
|
|
22
|
+
* skipIfUnavailable: async () => !(await hasDockerPg()),
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* ## What it asserts
|
|
27
|
+
*
|
|
28
|
+
* 1. Channel pub/sub cross-instance - publish on A, subscribe on B, B receives within 500ms.
|
|
29
|
+
* 2. Channel isolation - channels keyed differently don't cross-talk.
|
|
30
|
+
* 3. Lock mutex - two instances racing for the same advisory lock, only one wins; release lets the other acquire.
|
|
31
|
+
* 4. Lock hand-off - kill lock-holder, another instance picks it up.
|
|
32
|
+
* 5. Process signal resume - process suspended at race(), signal fires, process wakes and advances pc.
|
|
33
|
+
* 6. Process cross-instance signal - process running on A, emit from B, A resumes.
|
|
34
|
+
* 7. Process advisory lock - spawn same process path on two instances, only one runs.
|
|
35
|
+
*
|
|
36
|
+
* Each scenario is scoped tight enough to pinpoint the failing primitive.
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* Run the cluster conformance suite against an adapter.
|
|
40
|
+
*
|
|
41
|
+
* @param adapterName - Shows up in test output: "cluster-conformance/postgres"
|
|
42
|
+
* @param opts - How to spin up instances + setup/teardown hooks
|
|
43
|
+
*/
|
|
44
|
+
export function describeClusterConformance(adapterName, opts) {
|
|
45
|
+
void adapterName;
|
|
46
|
+
void opts;
|
|
47
|
+
throw new Error(`describeClusterConformance('${adapterName}', ...) is not yet implemented. ` +
|
|
48
|
+
'Scenarios are listed in the header comment.');
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=cluster-primitives.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cluster-primitives.js","sourceRoot":"","sources":["../../src/conformance/cluster-primitives.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAqCH;;;;;GAKG;AACH,MAAM,UAAU,0BAA0B,CACxC,WAAmB,EACnB,IAA+B;IAE/B,KAAK,WAAW,CAAC;IACjB,KAAK,IAAI,CAAC;IACV,MAAM,IAAI,KAAK,CACb,+BAA+B,WAAW,kCAAkC;QAC5E,6CAA6C,CAC9C,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Container
|
|
3
|
+
*
|
|
4
|
+
* Extended container with support for mocking and overriding services.
|
|
5
|
+
*/
|
|
6
|
+
import { Container, type ServiceToken, type InstanceOf } from '@justscale/core';
|
|
7
|
+
/**
|
|
8
|
+
* A container designed for testing with mock support.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* const container = new TestContainer();
|
|
13
|
+
*
|
|
14
|
+
* // Register real services
|
|
15
|
+
* container.register(UserRepository);
|
|
16
|
+
* container.register(UserService);
|
|
17
|
+
*
|
|
18
|
+
* // Override with a mock
|
|
19
|
+
* container.mock(UserRepository, {
|
|
20
|
+
* findById: mockFn().returns(Promise.resolve({ id: '1', name: 'Test' })),
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* // Resolve - will use the mock for UserRepository
|
|
24
|
+
* const userService = container.resolve(UserService);
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare class TestContainer extends Container {
|
|
28
|
+
private mocks;
|
|
29
|
+
/**
|
|
30
|
+
* Override a service with a mock implementation.
|
|
31
|
+
* The mock will be used instead of the real service when resolving.
|
|
32
|
+
*/
|
|
33
|
+
mock<T>(token: ServiceToken<T>, mockInstance: Partial<T>): this;
|
|
34
|
+
/**
|
|
35
|
+
* Override a service with a complete replacement instance.
|
|
36
|
+
*/
|
|
37
|
+
override<T>(token: ServiceToken<T>, instance: T): this;
|
|
38
|
+
/**
|
|
39
|
+
* Clear all mocks, restoring original service resolution.
|
|
40
|
+
*/
|
|
41
|
+
clearMocks(): this;
|
|
42
|
+
/**
|
|
43
|
+
* Clear a specific mock.
|
|
44
|
+
*/
|
|
45
|
+
clearMock<T>(token: ServiceToken<T>): this;
|
|
46
|
+
/**
|
|
47
|
+
* Get the mock for a service if one exists.
|
|
48
|
+
*/
|
|
49
|
+
getMock<T>(token: ServiceToken<T>): T | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* Check if a service has been mocked.
|
|
52
|
+
*/
|
|
53
|
+
isMocked<T>(token: ServiceToken<T>): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Resolve a service - returns mock if available, otherwise resolves normally.
|
|
56
|
+
*/
|
|
57
|
+
resolve<T>(token: ServiceToken<T>): Promise<T>;
|
|
58
|
+
/**
|
|
59
|
+
* Get typed access to a resolved service.
|
|
60
|
+
* Useful for accessing services in tests.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* const userService = await container.get(UserService);
|
|
65
|
+
* // userService is typed as InstanceOf<typeof UserService>
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
get<T extends ServiceToken>(token: T): Promise<InstanceOf<T>>;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=container.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,SAAS,EACT,KAAK,YAAY,EACjB,KAAK,UAAU,EAChB,MAAM,iBAAiB,CAAC;AAEzB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,aAAc,SAAQ,SAAS;IAC1C,OAAO,CAAC,KAAK,CAAoC;IAEjD;;;OAGG;IACH,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI;IAK/D;;OAEG;IACH,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI;IAKtD;;OAEG;IACH,UAAU,IAAI,IAAI;IAKlB;;OAEG;IACH,SAAS,CAAC,CAAC,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,IAAI;IAK1C;;OAEG;IACH,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,SAAS;IAIjD;;OAEG;IACH,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,OAAO;IAI5C;;OAEG;IACY,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAU7D;;;;;;;;;OASG;IACG,GAAG,CAAC,CAAC,SAAS,YAAY,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;CAGpE"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Container
|
|
3
|
+
*
|
|
4
|
+
* Extended container with support for mocking and overriding services.
|
|
5
|
+
*/
|
|
6
|
+
import { Container, } from '@justscale/core';
|
|
7
|
+
/**
|
|
8
|
+
* A container designed for testing with mock support.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* const container = new TestContainer();
|
|
13
|
+
*
|
|
14
|
+
* // Register real services
|
|
15
|
+
* container.register(UserRepository);
|
|
16
|
+
* container.register(UserService);
|
|
17
|
+
*
|
|
18
|
+
* // Override with a mock
|
|
19
|
+
* container.mock(UserRepository, {
|
|
20
|
+
* findById: mockFn().returns(Promise.resolve({ id: '1', name: 'Test' })),
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* // Resolve - will use the mock for UserRepository
|
|
24
|
+
* const userService = container.resolve(UserService);
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export class TestContainer extends Container {
|
|
28
|
+
mocks = new Map();
|
|
29
|
+
/**
|
|
30
|
+
* Override a service with a mock implementation.
|
|
31
|
+
* The mock will be used instead of the real service when resolving.
|
|
32
|
+
*/
|
|
33
|
+
mock(token, mockInstance) {
|
|
34
|
+
this.mocks.set(token, mockInstance);
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Override a service with a complete replacement instance.
|
|
39
|
+
*/
|
|
40
|
+
override(token, instance) {
|
|
41
|
+
this.mocks.set(token, instance);
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Clear all mocks, restoring original service resolution.
|
|
46
|
+
*/
|
|
47
|
+
clearMocks() {
|
|
48
|
+
this.mocks.clear();
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Clear a specific mock.
|
|
53
|
+
*/
|
|
54
|
+
clearMock(token) {
|
|
55
|
+
this.mocks.delete(token);
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get the mock for a service if one exists.
|
|
60
|
+
*/
|
|
61
|
+
getMock(token) {
|
|
62
|
+
return this.mocks.get(token);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Check if a service has been mocked.
|
|
66
|
+
*/
|
|
67
|
+
isMocked(token) {
|
|
68
|
+
return this.mocks.has(token);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Resolve a service - returns mock if available, otherwise resolves normally.
|
|
72
|
+
*/
|
|
73
|
+
async resolve(token) {
|
|
74
|
+
// Check for mock first
|
|
75
|
+
if (this.mocks.has(token)) {
|
|
76
|
+
return this.mocks.get(token);
|
|
77
|
+
}
|
|
78
|
+
// Fall back to normal resolution
|
|
79
|
+
return super.resolve(token);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get typed access to a resolved service.
|
|
83
|
+
* Useful for accessing services in tests.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* const userService = await container.get(UserService);
|
|
88
|
+
* // userService is typed as InstanceOf<typeof UserService>
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
async get(token) {
|
|
92
|
+
return this.resolve(token);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=container.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"container.js","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,SAAS,GAGV,MAAM,iBAAiB,CAAC;AAEzB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,OAAO,aAAc,SAAQ,SAAS;IAClC,KAAK,GAAG,IAAI,GAAG,EAAyB,CAAC;IAEjD;;;OAGG;IACH,IAAI,CAAI,KAAsB,EAAE,YAAwB;QACtD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;QACpC,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;OAEG;IACH,QAAQ,CAAI,KAAsB,EAAE,QAAW;QAC7C,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;QAChC,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;OAEG;IACH,UAAU;QACR,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;OAEG;IACH,SAAS,CAAI,KAAsB;QACjC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACzB,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;OAEG;IACH,OAAO,CAAI,KAAsB;QAC/B,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAkB,CAAC;IAChD,CAAC;IAED;;OAEG;IACH,QAAQ,CAAI,KAAsB;QAChC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAED;;OAEG;IACM,KAAK,CAAC,OAAO,CAAI,KAAsB;QAC9C,uBAAuB;QACvB,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAM,CAAC;QACpC,CAAC;QAED,iCAAiC;QACjC,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,GAAG,CAAyB,KAAQ;QACxC,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,CAA2B,CAAC;IACvD,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JustScale Testing Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides helpers for testing JustScale applications:
|
|
5
|
+
* - TestContainer: Container with mock support
|
|
6
|
+
* - createTestApp: Create an app instance for testing
|
|
7
|
+
* - Test client with pluggable transports
|
|
8
|
+
*/
|
|
9
|
+
export { TestContainer } from './container.js';
|
|
10
|
+
export { createTestApp, type TestApp } from './app.js';
|
|
11
|
+
export { createTestClient, teardownApp, type TestClient, type TestClientWithTransports, type TestClientOptions, type TestTransport, type TransportState, type TransportClient, type TransportOptions, type TestResponse, type BuildControllerAPI, } from './client.js';
|
|
12
|
+
export { createTestKit, type TestKit, type CreateTestKitOptions, type KitBuilderFn, type SpawnHttpOptions, type SpawnHttpResult, } from './kit.js';
|
|
13
|
+
export { mock, mockFn, mockService, spyOn, spyService, mockResolves, mockRejects, mockThrows, assertCalledWith, assertCallCount, assertNotCalled, enableDebuggerFormatters, type MockedService, type SpyWrapper, } from './mock.js';
|
|
14
|
+
export { InMemoryLockProvider } from '@justscale/core/memory';
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AASH,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,KAAK,OAAO,EAAE,MAAM,UAAU,CAAC;AACvD,OAAO,EACL,gBAAgB,EAChB,WAAW,EACX,KAAK,UAAU,EACf,KAAK,wBAAwB,EAC7B,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,aAAa,EACb,KAAK,OAAO,EACZ,KAAK,oBAAoB,EACzB,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,eAAe,GACrB,MAAM,UAAU,CAAC;AAClB,OAAO,EAEL,IAAI,EAEJ,MAAM,EACN,WAAW,EACX,KAAK,EACL,UAAU,EACV,YAAY,EACZ,WAAW,EACX,UAAU,EAEV,gBAAgB,EAChB,eAAe,EACf,eAAe,EAEf,wBAAwB,EAExB,KAAK,aAAa,EAClB,KAAK,UAAU,GAChB,MAAM,WAAW,CAAC;AAGnB,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JustScale Testing Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides helpers for testing JustScale applications:
|
|
5
|
+
* - TestContainer: Container with mock support
|
|
6
|
+
* - createTestApp: Create an app instance for testing
|
|
7
|
+
* - Test client with pluggable transports
|
|
8
|
+
*/
|
|
9
|
+
if (process.env.NODE_ENV === 'production') {
|
|
10
|
+
throw new Error('@justscale/core/testing must not be imported in production code. ' +
|
|
11
|
+
'This package contains test-only utilities (mocks, spies, TestContainer).');
|
|
12
|
+
}
|
|
13
|
+
export { TestContainer } from './container.js';
|
|
14
|
+
export { createTestApp } from './app.js';
|
|
15
|
+
export { createTestClient, teardownApp, } from './client.js';
|
|
16
|
+
export { createTestKit, } from './kit.js';
|
|
17
|
+
export {
|
|
18
|
+
// Re-exported from node:test
|
|
19
|
+
mock,
|
|
20
|
+
// Mock helpers
|
|
21
|
+
mockFn, mockService, spyOn, spyService, mockResolves, mockRejects, mockThrows,
|
|
22
|
+
// Assertions
|
|
23
|
+
assertCalledWith, assertCallCount, assertNotCalled,
|
|
24
|
+
// Debugger support
|
|
25
|
+
enableDebuggerFormatters, } from './mock.js';
|
|
26
|
+
// Lock testing utilities
|
|
27
|
+
export { InMemoryLockProvider } from '@justscale/core/memory';
|
|
28
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;IAC1C,MAAM,IAAI,KAAK,CACb,mEAAmE;QACnE,0EAA0E,CAC3E,CAAC;AACJ,CAAC;AAED,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAgB,MAAM,UAAU,CAAC;AACvD,OAAO,EACL,gBAAgB,EAChB,WAAW,GAUZ,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,aAAa,GAMd,MAAM,UAAU,CAAC;AAClB,OAAO;AACL,6BAA6B;AAC7B,IAAI;AACJ,eAAe;AACf,MAAM,EACN,WAAW,EACX,KAAK,EACL,UAAU,EACV,YAAY,EACZ,WAAW,EACX,UAAU;AACV,aAAa;AACb,gBAAgB,EAChB,eAAe,EACf,eAAe;AACf,mBAAmB;AACnB,wBAAwB,GAIzB,MAAM,WAAW,CAAC;AAEnB,yBAAyB;AACzB,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/kit.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import JustScale from '@justscale/core';
|
|
2
|
+
import type { App, ControllerDef } from '@justscale/core';
|
|
3
|
+
type JustScaleBuilder = ReturnType<typeof JustScale>;
|
|
4
|
+
import { type TestClientOptions, type TestClientWithTransports, type TestTransport, type BuildControllerAPI } from './client.js';
|
|
5
|
+
/**
|
|
6
|
+
* Builder factory: receives a fresh JustScale() builder (with the usual
|
|
7
|
+
* built-ins already provided — Logger, Lifecycle), returns it decorated
|
|
8
|
+
* with whatever the test wants. The returned Builder must have its
|
|
9
|
+
* requires satisfied; we call `.build().compile()` on it.
|
|
10
|
+
*/
|
|
11
|
+
export type KitBuilderFn = (b: JustScaleBuilder) => {
|
|
12
|
+
build: () => {
|
|
13
|
+
compile: () => App;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
export interface SpawnHttpOptions<TTransports extends Record<string, TestTransport<unknown, unknown>>, TControllers extends Record<string, ControllerDef<any, any, any>>> extends TestClientOptions<TTransports> {
|
|
17
|
+
/** Map of controller defs to surface as a typed `controllers` API. */
|
|
18
|
+
controllers?: TControllers;
|
|
19
|
+
}
|
|
20
|
+
export interface SpawnHttpResult<TTransports extends Record<string, TestTransport<unknown, unknown>>, TControllers extends Record<string, ControllerDef<any, any, any>>> {
|
|
21
|
+
app: App;
|
|
22
|
+
client: TestClientWithTransports<App, TTransports>;
|
|
23
|
+
controllers: BuildControllerAPI<TControllers>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Multi-instance test harness. Owns N apps, drains them on dispose.
|
|
27
|
+
*
|
|
28
|
+
* Auto-registers `afterEach` from node:test when available so tests
|
|
29
|
+
* never have to remember cleanup; `using` / explicit `dispose()` are
|
|
30
|
+
* supported as fallbacks (or for tests outside node:test).
|
|
31
|
+
*
|
|
32
|
+
* The framework's story is many-instance coordination — this kit
|
|
33
|
+
* makes 1 the trivial case of N. `spawnCluster(N, builder)` is the
|
|
34
|
+
* primitive for distributed-invariant tests (cross-instance lock,
|
|
35
|
+
* cross-instance channel) that previously required ad-hoc worker
|
|
36
|
+
* spawning.
|
|
37
|
+
*/
|
|
38
|
+
export interface TestKit {
|
|
39
|
+
/** Build + compile one app. Adds it to the kit's owned set. */
|
|
40
|
+
spawn(builderFn: KitBuilderFn): Promise<App>;
|
|
41
|
+
/**
|
|
42
|
+
* Build + compile one app AND attach test transport(s) for HTTP-style
|
|
43
|
+
* testing. Returns a flat result: `{ app, client, controllers }`.
|
|
44
|
+
* `controllers` is the typed surface for the controller defs you pass.
|
|
45
|
+
*/
|
|
46
|
+
spawnHttp<TTransports extends Record<string, TestTransport<unknown, unknown>> = Record<string, never>, TControllers extends Record<string, ControllerDef<any, any, any>> = Record<string, never>>(builderFn: KitBuilderFn, options?: SpawnHttpOptions<TTransports, TControllers>): Promise<SpawnHttpResult<TTransports, TControllers>>;
|
|
47
|
+
/**
|
|
48
|
+
* N identical instances. Use for distributed-invariant tests:
|
|
49
|
+
* the apps share infrastructure (same Postgres, same Redis) so
|
|
50
|
+
* coordination primitives (locks, channels) operate cross-instance.
|
|
51
|
+
*/
|
|
52
|
+
spawnCluster(n: number, builderFn: KitBuilderFn): Promise<App[]>;
|
|
53
|
+
/** Drain everything. Idempotent. */
|
|
54
|
+
dispose(): Promise<void>;
|
|
55
|
+
/** Apps owned by this kit (live + already-disposed are pruned). */
|
|
56
|
+
readonly apps: readonly App[];
|
|
57
|
+
readonly [Symbol.asyncDispose]: () => Promise<void>;
|
|
58
|
+
}
|
|
59
|
+
export interface CreateTestKitOptions {
|
|
60
|
+
/**
|
|
61
|
+
* Whether to auto-register `afterEach` from node:test. Defaults to
|
|
62
|
+
* true. Set to false for tests that manage their own lifecycle.
|
|
63
|
+
*/
|
|
64
|
+
autoCleanup?: boolean;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Create a multi-instance test harness. Call at module level (top of
|
|
68
|
+
* a test file) so its `afterEach` runs after every test in the file.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* const kit = createTestKit()
|
|
73
|
+
*
|
|
74
|
+
* test('lock cross-instance', async () => {
|
|
75
|
+
* const [a, b] = await kit.spawnCluster(2, builder)
|
|
76
|
+
* // ... a holds lock, b times out, etc.
|
|
77
|
+
* })
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export declare function createTestKit(options?: CreateTestKitOptions): TestKit;
|
|
81
|
+
export {};
|
|
82
|
+
//# sourceMappingURL=kit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kit.d.ts","sourceRoot":"","sources":["../src/kit.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,iBAAiB,CAAC;AACxC,OAAO,KAAK,EAAE,GAAG,EAAW,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEnE,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AACrD,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACxB,MAAM,aAAa,CAAC;AAErB;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,CACzB,CAAC,EAAE,gBAAgB,KAChB;IAAE,KAAK,EAAE,MAAM;QAAE,OAAO,EAAE,MAAM,GAAG,CAAA;KAAE,CAAA;CAAE,CAAC;AAE7C,MAAM,WAAW,gBAAgB,CAC/B,WAAW,SAAS,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,EACnE,YAAY,SAAS,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CACjE,SAAQ,iBAAiB,CAAC,WAAW,CAAC;IACtC,sEAAsE;IACtE,WAAW,CAAC,EAAE,YAAY,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe,CAC9B,WAAW,SAAS,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,EACnE,YAAY,SAAS,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IAEjE,GAAG,EAAE,GAAG,CAAC;IACT,MAAM,EAAE,wBAAwB,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;IACnD,WAAW,EAAE,kBAAkB,CAAC,YAAY,CAAC,CAAC;CAC/C;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,OAAO;IACtB,+DAA+D;IAC/D,KAAK,CAAC,SAAS,EAAE,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAE7C;;;;OAIG;IACH,SAAS,CACP,WAAW,SAAS,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,EAC3F,YAAY,SAAS,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,EAEzF,SAAS,EAAE,YAAY,EACvB,OAAO,CAAC,EAAE,gBAAgB,CAAC,WAAW,EAAE,YAAY,CAAC,GACpD,OAAO,CAAC,eAAe,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC,CAAC;IAEvD;;;;OAIG;IACH,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,YAAY,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAEjE,oCAAoC;IACpC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzB,mEAAmE;IACnE,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,EAAE,CAAC;IAE9B,QAAQ,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACrD;AAED,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CA2GrE"}
|
package/dist/kit.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import JustScale from '@justscale/core';
|
|
2
|
+
import { createTestClient, teardownApp, } from './client.js';
|
|
3
|
+
/**
|
|
4
|
+
* Create a multi-instance test harness. Call at module level (top of
|
|
5
|
+
* a test file) so its `afterEach` runs after every test in the file.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const kit = createTestKit()
|
|
10
|
+
*
|
|
11
|
+
* test('lock cross-instance', async () => {
|
|
12
|
+
* const [a, b] = await kit.spawnCluster(2, builder)
|
|
13
|
+
* // ... a holds lock, b times out, etc.
|
|
14
|
+
* })
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export function createTestKit(options) {
|
|
18
|
+
const owned = [];
|
|
19
|
+
let disposed = false;
|
|
20
|
+
async function disposeAll() {
|
|
21
|
+
if (disposed)
|
|
22
|
+
return;
|
|
23
|
+
disposed = true;
|
|
24
|
+
const errors = [];
|
|
25
|
+
while (owned.length) {
|
|
26
|
+
const entry = owned.pop();
|
|
27
|
+
try {
|
|
28
|
+
await entry.close();
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
errors.push(err);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
disposed = false; // allow re-spawn after manual dispose mid-test
|
|
35
|
+
if (errors.length) {
|
|
36
|
+
const first = errors[0];
|
|
37
|
+
throw first instanceof Error ? first : new Error(String(first));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (options?.autoCleanup ?? true) {
|
|
41
|
+
// node:test exposes `afterEach` as a top-level export; if we're
|
|
42
|
+
// running outside node:test (e.g. a script), this import fails
|
|
43
|
+
// silently and callers must dispose manually.
|
|
44
|
+
void (async () => {
|
|
45
|
+
try {
|
|
46
|
+
const { afterEach } = await import('node:test');
|
|
47
|
+
afterEach(async () => {
|
|
48
|
+
await disposeAll();
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// not in node:test context
|
|
53
|
+
}
|
|
54
|
+
})();
|
|
55
|
+
}
|
|
56
|
+
async function buildAndCompile(builderFn) {
|
|
57
|
+
const builder = builderFn(JustScale());
|
|
58
|
+
const app = builder.build().compile();
|
|
59
|
+
await app.ready;
|
|
60
|
+
return app;
|
|
61
|
+
}
|
|
62
|
+
const kit = {
|
|
63
|
+
async spawn(builderFn) {
|
|
64
|
+
const app = await buildAndCompile(builderFn);
|
|
65
|
+
owned.push({ app, close: () => teardownApp(app) });
|
|
66
|
+
return app;
|
|
67
|
+
},
|
|
68
|
+
async spawnHttp(builderFn, opts) {
|
|
69
|
+
const app = await buildAndCompile(builderFn);
|
|
70
|
+
const transports = opts?.transports ?? {};
|
|
71
|
+
const transportOptions = opts?.transportOptions;
|
|
72
|
+
const client = await createTestClient(app, {
|
|
73
|
+
transports,
|
|
74
|
+
transportOptions,
|
|
75
|
+
});
|
|
76
|
+
// client.close() already calls teardownApp
|
|
77
|
+
owned.push({ app, close: () => client.close() });
|
|
78
|
+
const controllerDefs = (opts?.controllers ?? {});
|
|
79
|
+
// useControllers lives on the http transport client; only available if the
|
|
80
|
+
// caller wired the http transport.
|
|
81
|
+
const httpClient = client['http'];
|
|
82
|
+
const controllers = httpClient && typeof httpClient.useControllers === 'function' && Object.keys(controllerDefs).length
|
|
83
|
+
? httpClient.useControllers(controllerDefs)
|
|
84
|
+
: {};
|
|
85
|
+
return {
|
|
86
|
+
app,
|
|
87
|
+
client: client,
|
|
88
|
+
controllers: controllers,
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
async spawnCluster(n, builderFn) {
|
|
92
|
+
if (n < 1)
|
|
93
|
+
return [];
|
|
94
|
+
const results = [];
|
|
95
|
+
// Sequential: deterministic ordering for distributed tests, and the
|
|
96
|
+
// build phase is fast (DI graph, not network). Parallel build was
|
|
97
|
+
// an option but it makes timing-sensitive tests harder to reason
|
|
98
|
+
// about for trivial speedup.
|
|
99
|
+
for (let i = 0; i < n; i++) {
|
|
100
|
+
const app = await buildAndCompile(builderFn);
|
|
101
|
+
owned.push({ app, close: () => teardownApp(app) });
|
|
102
|
+
results.push(app);
|
|
103
|
+
}
|
|
104
|
+
return results;
|
|
105
|
+
},
|
|
106
|
+
dispose: disposeAll,
|
|
107
|
+
get apps() {
|
|
108
|
+
return owned.map((e) => e.app);
|
|
109
|
+
},
|
|
110
|
+
[Symbol.asyncDispose]: () => disposeAll(),
|
|
111
|
+
};
|
|
112
|
+
return kit;
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=kit.js.map
|
package/dist/kit.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kit.js","sourceRoot":"","sources":["../src/kit.ts"],"names":[],"mappings":"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"}
|
package/dist/mock.d.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mocking utilities for JustScale testing
|
|
3
|
+
*
|
|
4
|
+
* Integrates with Node.js test runner's mock API while providing
|
|
5
|
+
* type-safe helpers for mocking services and controllers.
|
|
6
|
+
*
|
|
7
|
+
* Supports `using` keyword for automatic cleanup:
|
|
8
|
+
* ```typescript
|
|
9
|
+
* using spied = spyOn(service);
|
|
10
|
+
* // ... test code ...
|
|
11
|
+
* // automatically restored when block exits
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
import { mock, type Mock as NodeMock } from 'node:test';
|
|
15
|
+
import type { ServiceDef, ServiceToken } from '@justscale/core';
|
|
16
|
+
export { mock };
|
|
17
|
+
/**
|
|
18
|
+
* Manually enable custom formatters after debugger connects.
|
|
19
|
+
* This is opt-in — it is not called automatically on import.
|
|
20
|
+
* Call it early in your test setup if you want CDP-based formatting in the debugger.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* import { enableDebuggerFormatters } from '@justscale/testing';
|
|
25
|
+
* enableDebuggerFormatters(); // Call early in your test
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare function enableDebuggerFormatters(): void;
|
|
29
|
+
/**
|
|
30
|
+
* Extract the instance type from a ServiceDef or ServiceToken
|
|
31
|
+
*/
|
|
32
|
+
type ServiceInstance<T> = T extends ServiceDef<infer S, any> ? S : T extends ServiceToken<infer S> ? S : never;
|
|
33
|
+
/**
|
|
34
|
+
* A mocked version of a service where all methods are mock functions
|
|
35
|
+
*/
|
|
36
|
+
export type MockedService<T> = {
|
|
37
|
+
[K in keyof T]: T[K] extends (...args: infer A) => infer R ? NodeMock<(...args: A) => R> : T[K];
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* A spy wrapper that can be disposed to restore original methods
|
|
41
|
+
*/
|
|
42
|
+
export interface SpyWrapper<T> extends Disposable {
|
|
43
|
+
/** The spied object with mock functions */
|
|
44
|
+
readonly spied: MockedService<T>;
|
|
45
|
+
/** The original object */
|
|
46
|
+
readonly original: T;
|
|
47
|
+
/** Reset all mock call tracking */
|
|
48
|
+
reset(): void;
|
|
49
|
+
/** Restore original methods (also called on dispose) */
|
|
50
|
+
restore(): void;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Create a mock implementation of a service.
|
|
54
|
+
* Uses node:test mock functions for tracking calls and assertions.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* const mockRepo = mockService<typeof PlayerRepository>({
|
|
59
|
+
* get: mock.fn(() => Promise.resolve({ id: '1', name: 'Mock' })),
|
|
60
|
+
* find: mock.fn(() => Promise.resolve([])),
|
|
61
|
+
* });
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export declare function mockService<T extends ServiceToken>(impl: Partial<MockedService<ServiceInstance<T>>>, name?: string): MockedService<ServiceInstance<T>>;
|
|
65
|
+
/**
|
|
66
|
+
* Create a spy wrapper around an existing service instance.
|
|
67
|
+
* All method calls are tracked while still calling the real implementation.
|
|
68
|
+
*
|
|
69
|
+
* Supports `using` for automatic cleanup:
|
|
70
|
+
* ```typescript
|
|
71
|
+
* using spy = spyOn(playerRepo);
|
|
72
|
+
* await spy.spied.get(Player.ref('123'));
|
|
73
|
+
* assertCallCount(spy.spied.get, 1);
|
|
74
|
+
* // Original methods restored when block exits
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* @example Manual cleanup
|
|
78
|
+
* ```typescript
|
|
79
|
+
* const spy = spyOn(playerRepo);
|
|
80
|
+
* try {
|
|
81
|
+
* await spy.spied.get(Player.ref('123'));
|
|
82
|
+
* assertCallCount(spy.spied.get, 1);
|
|
83
|
+
* } finally {
|
|
84
|
+
* spy.restore();
|
|
85
|
+
* }
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export declare function spyOn<T extends object>(instance: T): SpyWrapper<T>;
|
|
89
|
+
/**
|
|
90
|
+
* Create a spy that wraps an existing service instance.
|
|
91
|
+
* Returns just the spied object (simpler API, no auto-cleanup).
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* const spiedRepo = spyService(playerRepo);
|
|
96
|
+
* await spiedRepo.get(Player.ref('123'));
|
|
97
|
+
* assertCallCount(spiedRepo.get, 1);
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export declare function spyService<T extends object>(instance: T): MockedService<T>;
|
|
101
|
+
/**
|
|
102
|
+
* Create a mock function with a specific implementation.
|
|
103
|
+
* @param implementation Optional function to call when the mock is invoked
|
|
104
|
+
* @param name Optional name for debugging (shown in console.log)
|
|
105
|
+
*/
|
|
106
|
+
export declare function mockFn<TArgs extends unknown[], TReturn>(implementation?: (...args: TArgs) => TReturn, name?: string): NodeMock<(...args: TArgs) => TReturn>;
|
|
107
|
+
/**
|
|
108
|
+
* Create a mock function that returns a resolved promise.
|
|
109
|
+
* @param value The value to resolve with
|
|
110
|
+
* @param name Optional name for debugging (shown in console.log)
|
|
111
|
+
*/
|
|
112
|
+
export declare function mockResolves<T>(value: T, name?: string): NodeMock<(...args: unknown[]) => Promise<T>>;
|
|
113
|
+
/**
|
|
114
|
+
* Create a mock function that returns a rejected promise.
|
|
115
|
+
* @param error The error to reject with
|
|
116
|
+
* @param name Optional name for debugging (shown in console.log)
|
|
117
|
+
*/
|
|
118
|
+
export declare function mockRejects(error: Error, name?: string): NodeMock<(...args: unknown[]) => Promise<never>>;
|
|
119
|
+
/**
|
|
120
|
+
* Create a mock function that throws an error.
|
|
121
|
+
* @param error The error to throw
|
|
122
|
+
* @param name Optional name for debugging (shown in console.log)
|
|
123
|
+
*/
|
|
124
|
+
export declare function mockThrows(error: Error, name?: string): NodeMock<(...args: unknown[]) => never>;
|
|
125
|
+
/**
|
|
126
|
+
* Assert that a mock was called with specific arguments.
|
|
127
|
+
*/
|
|
128
|
+
export declare function assertCalledWith<TArgs extends unknown[], TReturn>(mockFn: NodeMock<(...args: TArgs) => TReturn>, ...expectedArgs: TArgs): void;
|
|
129
|
+
/**
|
|
130
|
+
* Assert that a mock was called exactly n times.
|
|
131
|
+
*/
|
|
132
|
+
export declare function assertCallCount<TArgs extends unknown[], TReturn>(mockFn: NodeMock<(...args: TArgs) => TReturn>, expected: number): void;
|
|
133
|
+
/**
|
|
134
|
+
* Assert that a mock was never called.
|
|
135
|
+
*/
|
|
136
|
+
export declare function assertNotCalled<TArgs extends unknown[], TReturn>(mockFn: NodeMock<(...args: TArgs) => TReturn>): void;
|
|
137
|
+
//# sourceMappingURL=mock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock.d.ts","sourceRoot":"","sources":["../src/mock.ts"],"names":[],"mappings":"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"}
|