@dever-labs/mockly-testcontainers 0.12.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/README.md +98 -0
- package/dist/MocklyContainer.d.ts +33 -0
- package/dist/MocklyContainer.js +112 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/types.d.ts +22 -0
- package/dist/types.js +1 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# @dever-labs/mockly-testcontainers
|
|
2
|
+
|
|
3
|
+
Run Mockly in Docker-backed Node.js and TypeScript tests with `testcontainers`.
|
|
4
|
+
|
|
5
|
+
The package starts `ghcr.io/dever-labs/mockly:latest`, waits for the management API to be ready, and exposes helper methods for mocks, scenarios, faults, and logs.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
- Node 18+
|
|
10
|
+
- Docker
|
|
11
|
+
- `testcontainers` 10+
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
npm i -D @dever-labs/mockly-testcontainers testcontainers
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quickstart
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import assert from 'node:assert/strict'
|
|
23
|
+
import { MocklyContainerBuilder } from '@dever-labs/mockly-testcontainers'
|
|
24
|
+
|
|
25
|
+
const container = await new MocklyContainerBuilder().start()
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
await container.addMock({
|
|
29
|
+
id: 'get-user',
|
|
30
|
+
request: { method: 'GET', path: '/users/1' },
|
|
31
|
+
response: { status: 200, body: '{"id":1}' },
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const response = await fetch(`${container.getHttpBase()}/users/1`)
|
|
35
|
+
assert.equal(response.status, 200)
|
|
36
|
+
assert.equal(await response.text(), '{"id":1}')
|
|
37
|
+
} finally {
|
|
38
|
+
await container.stop()
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## When to use the testcontainers module
|
|
43
|
+
|
|
44
|
+
Use `@dever-labs/mockly-testcontainers` when you want Docker-managed lifecycle, no native binary download, or a consistent Mockly environment across laptops and CI.
|
|
45
|
+
|
|
46
|
+
Use `@dever-labs/mockly-driver` when you want to run the Mockly binary directly in the test process instead.
|
|
47
|
+
|
|
48
|
+
## Builder API
|
|
49
|
+
|
|
50
|
+
`MocklyContainerBuilder` configures the container before startup.
|
|
51
|
+
|
|
52
|
+
| Method | Description |
|
|
53
|
+
|---|---|
|
|
54
|
+
| `new MocklyContainerBuilder()` | Create a builder with the default image and default config. |
|
|
55
|
+
| `withImage(image)` | Override the Docker image. |
|
|
56
|
+
| `withInlineConfig(yaml)` | Replace `/config/mockly.yaml` with inline YAML. |
|
|
57
|
+
| `start()` | Start the container and return a `StartedMocklyContainer`. |
|
|
58
|
+
|
|
59
|
+
### Custom YAML config
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
const container = await new MocklyContainerBuilder()
|
|
63
|
+
.withInlineConfig(`mockly:
|
|
64
|
+
api:
|
|
65
|
+
port: 9091
|
|
66
|
+
protocols:
|
|
67
|
+
http:
|
|
68
|
+
enabled: true
|
|
69
|
+
port: 8090
|
|
70
|
+
`)
|
|
71
|
+
.start()
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Started container API
|
|
75
|
+
|
|
76
|
+
| Method | Description |
|
|
77
|
+
|---|---|
|
|
78
|
+
| `getHttpBase()` | Base URL of the mock HTTP server. |
|
|
79
|
+
| `getApiBase()` | Base URL of the management API. |
|
|
80
|
+
| `stop()` | Stop and remove the container. |
|
|
81
|
+
| `addMock(mock)` | Register a dynamic HTTP mock. |
|
|
82
|
+
| `deleteMock(id)` | Delete a mock by ID. |
|
|
83
|
+
| `reset()` | Remove dynamic mocks, deactivate scenarios, and clear faults. |
|
|
84
|
+
| `activateScenario(id)` | Activate a configured scenario. |
|
|
85
|
+
| `deactivateScenario(id)` | Deactivate a configured scenario. |
|
|
86
|
+
| `setFault(config)` | Apply a global HTTP fault. |
|
|
87
|
+
| `clearFault()` | Remove the active fault. |
|
|
88
|
+
| `getLogs()` | Read request logs as JSON. |
|
|
89
|
+
| `clearLogs()` | Clear stored request logs. |
|
|
90
|
+
|
|
91
|
+
## Exported types
|
|
92
|
+
|
|
93
|
+
The package also exports these TypeScript types:
|
|
94
|
+
|
|
95
|
+
- `HttpMock`
|
|
96
|
+
- `MockRequest`
|
|
97
|
+
- `MockResponse`
|
|
98
|
+
- `FaultConfig`
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { StartedTestContainer } from 'testcontainers';
|
|
2
|
+
import type { HttpMock, FaultConfig } from './types.js';
|
|
3
|
+
export declare const DEFAULT_IMAGE = "ghcr.io/dever-labs/mockly:latest";
|
|
4
|
+
export declare const HTTP_PORT = 8090;
|
|
5
|
+
export declare const API_PORT = 9091;
|
|
6
|
+
export declare const CONTAINER_CONFIG_PATH = "/config/mockly.yaml";
|
|
7
|
+
export declare class MocklyContainerBuilder {
|
|
8
|
+
private image;
|
|
9
|
+
private inlineConfig;
|
|
10
|
+
withImage(image: string): this;
|
|
11
|
+
withInlineConfig(yaml: string): this;
|
|
12
|
+
start(): Promise<StartedMocklyContainer>;
|
|
13
|
+
}
|
|
14
|
+
export declare class StartedMocklyContainer {
|
|
15
|
+
private readonly container;
|
|
16
|
+
constructor(container: StartedTestContainer);
|
|
17
|
+
getHttpBase(): string;
|
|
18
|
+
getApiBase(): string;
|
|
19
|
+
stop(): Promise<void>;
|
|
20
|
+
addMock(mock: HttpMock): Promise<void>;
|
|
21
|
+
deleteMock(id: string): Promise<void>;
|
|
22
|
+
reset(): Promise<void>;
|
|
23
|
+
activateScenario(id: string): Promise<void>;
|
|
24
|
+
deactivateScenario(id: string): Promise<void>;
|
|
25
|
+
setFault(config: FaultConfig): Promise<void>;
|
|
26
|
+
clearFault(): Promise<void>;
|
|
27
|
+
getLogs(): Promise<string>;
|
|
28
|
+
clearLogs(): Promise<void>;
|
|
29
|
+
private assertOk;
|
|
30
|
+
private get;
|
|
31
|
+
private post;
|
|
32
|
+
private delete;
|
|
33
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { GenericContainer, Wait } from 'testcontainers';
|
|
2
|
+
export const DEFAULT_IMAGE = 'ghcr.io/dever-labs/mockly:latest';
|
|
3
|
+
export const HTTP_PORT = 8090;
|
|
4
|
+
export const API_PORT = 9091;
|
|
5
|
+
export const CONTAINER_CONFIG_PATH = '/config/mockly.yaml';
|
|
6
|
+
const DEFAULT_CONFIG = `mockly:
|
|
7
|
+
api:
|
|
8
|
+
port: 9091
|
|
9
|
+
protocols:
|
|
10
|
+
http:
|
|
11
|
+
enabled: true
|
|
12
|
+
port: 8090
|
|
13
|
+
`;
|
|
14
|
+
export class MocklyContainerBuilder {
|
|
15
|
+
image = DEFAULT_IMAGE;
|
|
16
|
+
inlineConfig;
|
|
17
|
+
withImage(image) {
|
|
18
|
+
this.image = image;
|
|
19
|
+
return this;
|
|
20
|
+
}
|
|
21
|
+
withInlineConfig(yaml) {
|
|
22
|
+
this.inlineConfig = yaml;
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
async start() {
|
|
26
|
+
const configYaml = this.inlineConfig ?? DEFAULT_CONFIG;
|
|
27
|
+
const cmd = ['start', '-c', CONTAINER_CONFIG_PATH];
|
|
28
|
+
const container = await new GenericContainer(this.image)
|
|
29
|
+
.withExposedPorts(HTTP_PORT, API_PORT)
|
|
30
|
+
.withCommand(cmd)
|
|
31
|
+
.withCopyContentToContainer([
|
|
32
|
+
{
|
|
33
|
+
content: Buffer.from(configYaml),
|
|
34
|
+
target: CONTAINER_CONFIG_PATH,
|
|
35
|
+
mode: 0o644,
|
|
36
|
+
},
|
|
37
|
+
])
|
|
38
|
+
.withWaitStrategy(Wait.forHttp('/api/protocols', API_PORT).withStartupTimeout(60_000))
|
|
39
|
+
.start();
|
|
40
|
+
return new StartedMocklyContainer(container);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export class StartedMocklyContainer {
|
|
44
|
+
container;
|
|
45
|
+
constructor(container) {
|
|
46
|
+
this.container = container;
|
|
47
|
+
}
|
|
48
|
+
getHttpBase() {
|
|
49
|
+
return `http://${this.container.getHost()}:${this.container.getMappedPort(HTTP_PORT)}`;
|
|
50
|
+
}
|
|
51
|
+
getApiBase() {
|
|
52
|
+
return `http://${this.container.getHost()}:${this.container.getMappedPort(API_PORT)}`;
|
|
53
|
+
}
|
|
54
|
+
async stop() {
|
|
55
|
+
await this.container.stop();
|
|
56
|
+
}
|
|
57
|
+
async addMock(mock) {
|
|
58
|
+
const res = await this.post('/api/mocks/http', mock);
|
|
59
|
+
this.assertOk(res, `addMock(${mock.id})`);
|
|
60
|
+
}
|
|
61
|
+
async deleteMock(id) {
|
|
62
|
+
const res = await this.delete(`/api/mocks/http/${id}`);
|
|
63
|
+
this.assertOk(res, `deleteMock(${id})`);
|
|
64
|
+
}
|
|
65
|
+
async reset() {
|
|
66
|
+
const res = await this.post('/api/reset', null);
|
|
67
|
+
this.assertOk(res, 'reset');
|
|
68
|
+
}
|
|
69
|
+
async activateScenario(id) {
|
|
70
|
+
const res = await this.post(`/api/scenarios/${id}/activate`, null);
|
|
71
|
+
this.assertOk(res, `activateScenario(${id})`);
|
|
72
|
+
}
|
|
73
|
+
async deactivateScenario(id) {
|
|
74
|
+
const res = await this.post(`/api/scenarios/${id}/deactivate`, null);
|
|
75
|
+
this.assertOk(res, `deactivateScenario(${id})`);
|
|
76
|
+
}
|
|
77
|
+
async setFault(config) {
|
|
78
|
+
const res = await this.post('/api/fault', config);
|
|
79
|
+
this.assertOk(res, 'setFault');
|
|
80
|
+
}
|
|
81
|
+
async clearFault() {
|
|
82
|
+
const res = await this.delete('/api/fault');
|
|
83
|
+
this.assertOk(res, 'clearFault');
|
|
84
|
+
}
|
|
85
|
+
async getLogs() {
|
|
86
|
+
const res = await this.get('/api/logs');
|
|
87
|
+
this.assertOk(res, 'getLogs');
|
|
88
|
+
return await res.text();
|
|
89
|
+
}
|
|
90
|
+
async clearLogs() {
|
|
91
|
+
const res = await this.delete('/api/logs');
|
|
92
|
+
this.assertOk(res, 'clearLogs');
|
|
93
|
+
}
|
|
94
|
+
assertOk(res, operation) {
|
|
95
|
+
if (!res.ok) {
|
|
96
|
+
throw new Error(`${operation} failed: HTTP ${res.status}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async get(path) {
|
|
100
|
+
return fetch(`${this.getApiBase()}${path}`);
|
|
101
|
+
}
|
|
102
|
+
async post(path, body) {
|
|
103
|
+
return fetch(`${this.getApiBase()}${path}`, {
|
|
104
|
+
method: 'POST',
|
|
105
|
+
headers: body !== null ? { 'Content-Type': 'application/json' } : {},
|
|
106
|
+
body: body !== null ? JSON.stringify(body) : undefined,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
async delete(path) {
|
|
110
|
+
return fetch(`${this.getApiBase()}${path}`, { method: 'DELETE' });
|
|
111
|
+
}
|
|
112
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { CONTAINER_CONFIG_PATH, MocklyContainerBuilder, StartedMocklyContainer, DEFAULT_IMAGE, HTTP_PORT, API_PORT, } from './MocklyContainer.js';
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface MockRequest {
|
|
2
|
+
method: string;
|
|
3
|
+
path: string;
|
|
4
|
+
headers?: Record<string, string>;
|
|
5
|
+
}
|
|
6
|
+
export interface MockResponse {
|
|
7
|
+
status: number;
|
|
8
|
+
body?: string;
|
|
9
|
+
headers?: Record<string, string>;
|
|
10
|
+
delay?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface HttpMock {
|
|
13
|
+
id: string;
|
|
14
|
+
request: MockRequest;
|
|
15
|
+
response: MockResponse;
|
|
16
|
+
}
|
|
17
|
+
export interface FaultConfig {
|
|
18
|
+
enabled: boolean;
|
|
19
|
+
delay?: string;
|
|
20
|
+
status_override?: number;
|
|
21
|
+
error_rate?: number;
|
|
22
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dever-labs/mockly-testcontainers",
|
|
3
|
+
"version": "0.12.0",
|
|
4
|
+
"description": "Testcontainers module for Mockly — run Mockly as a Docker container in tests",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/dever-labs/mockly.git",
|
|
11
|
+
"directory": "clients/node-testcontainers"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"import": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc --project tsconfig.json",
|
|
25
|
+
"typecheck": "tsc --project tsconfig.json --noEmit",
|
|
26
|
+
"test": "vitest run",
|
|
27
|
+
"test:integration": "INTEGRATION=1 vitest run --config vitest.integration.config.ts",
|
|
28
|
+
"prepublishOnly": "npm run build"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18.0.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"testcontainers": ">=10.0.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^22.0.0",
|
|
38
|
+
"testcontainers": "^12.0.3",
|
|
39
|
+
"typescript": "^5.0.0",
|
|
40
|
+
"vitest": "^4.1.2"
|
|
41
|
+
},
|
|
42
|
+
"keywords": [
|
|
43
|
+
"mock",
|
|
44
|
+
"testing",
|
|
45
|
+
"http",
|
|
46
|
+
"integration-test",
|
|
47
|
+
"mockly",
|
|
48
|
+
"testcontainers",
|
|
49
|
+
"docker"
|
|
50
|
+
]
|
|
51
|
+
}
|