@hellaweb3/wiremock-testcontainers-nodejs 0.2.1 → 0.4.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 CHANGED
@@ -1,405 +1,206 @@
1
- <!--suppress HtmlDeprecatedAttribute -->
2
- <div align="center">
1
+ # WireMock Testcontainers for Node.js
3
2
 
4
- <h1>WireMock Testcontainers Node.js Typescript</h1>
3
+ [![Build](https://github.com/hella-web3/wiremock-testcontainers-nodejs/actions/workflows/main.yml/badge.svg)](https://github.com/hella-web3/wiremock-testcontainers-nodejs/actions/workflows/main.yml)
4
+ [![npm](https://img.shields.io/npm/v/%40hellaweb3%2Fwiremock-testcontainers-nodejs)](https://www.npmjs.com/package/@hellaweb3/wiremock-testcontainers-nodejs)
5
+ [![downloads](https://img.shields.io/npm/dm/%40hellaweb3%2Fwiremock-testcontainers-nodejs)](https://www.npmjs.com/package/@hellaweb3/wiremock-testcontainers-nodejs)
5
6
 
6
- <p>A library for easily using WireMock with Testcontainers in Node.js applications.</p>
7
+ Run [WireMock](https://wiremock.org/) in [Testcontainers](https://node.testcontainers.org/) with a
8
+ fluent TypeScript API modelled on the official
9
+ [Java module](https://github.com/wiremock/wiremock-testcontainers-java): load stubs from
10
+ directories, files, npm packages or inline JSON; reset state between tests without restarting;
11
+ verify calls through WireMock's request journal.
7
12
 
8
- <a href="https://github.com/hella-web3/wiremock-testcontainers-nodejs/actions/workflows/main.yml ">
9
- <img alt="Build Status" src="https://github.com/hella-web3/wiremock-testcontainers-nodejs/actions/workflows/main.yml/badge.svg" />
10
- </a>
11
-
12
- <a href="https://www.npmjs.com/package/@hellaweb3/wiremock-testcontainers-nodejs">
13
- <img alt="npm version" src="https://img.shields.io/npm/v/%40hellaweb3%2Fwiremock-testcontainers-nodejs" />
14
- </a>
15
-
16
- <a href="https://www.npmjs.com/package/@hellaweb3/wiremock-testcontainers-nodejs">
17
- <img alt="npm downloads" src="https://img.shields.io/npm/dm/%40hellaweb3%2Fwiremock-testcontainers-nodejs" />
18
- </a>
19
-
20
- </div>
21
-
22
- ## Description
23
-
24
- `wiremock-testcontainers-nodejs` is a Typescript nodejs library that spins up
25
- a [WireMock](https://wiremock.org/)
26
- instance with [Testcontainers](https://node.testcontainers.org/). It provides a
27
- fluent API to configure and start WireMock
28
- containers from recorded WireMock directories, individual named mappings
29
- (local files, npm packages, module-relative files or inline JSON) and static
30
- files – an API modelled on the official
31
- [Java module](https://github.com/wiremock/wiremock-testcontainers-java).
32
-
33
- ## Installation
13
+ ## Install
34
14
 
35
15
  ```bash
36
- pnpm add -D @hellaweb3/wiremock-testcontainers-nodejs testcontainers
16
+ npm i -D @hellaweb3/wiremock-testcontainers-nodejs testcontainers
37
17
  ```
38
18
 
39
- Requires Node.js >= 22.22 and a reachable Docker daemon (anything
40
- [Testcontainers](https://node.testcontainers.org/supported-container-runtimes/)
41
- supports).
19
+ Node.js >= 22.22 and a [container runtime](https://node.testcontainers.org/supported-container-runtimes/).
20
+ ESM and CJS, fully typed, `testcontainers` is the only dependency. pnpm >= 10 may report
21
+ `Ignored build scripts: ssh2, …` – optional native bindings of `testcontainers`, safe to ignore.
42
22
 
43
- **What you get:** a ~17 kB package – dual ESM (`dist/index.js`) and CJS
44
- (`dist/index.cjs`) builds with matching declarations, `sideEffects: false`
45
- for tree-shaking, `testcontainers` as the single runtime dependency (never
46
- bundled), and the TypeScript source for go-to-definition.
23
+ ## Quick start
47
24
 
48
- **pnpm users:** pnpm >= 10 may report `Ignored build scripts: ssh2, protobufjs,
49
- cpu-features`. These are transitive dependencies of `testcontainers`; the
50
- scripts only build an *optional* native crypto binding for `ssh2`, which falls
51
- back to pure JavaScript. It is safe to leave them ignored.
25
+ ```typescript
26
+ import { afterAll, afterEach, beforeAll, expect, it } from "vitest"; // or Jest
27
+ import { type StartedWiremockContainer, WiremockContainer } from "@hellaweb3/wiremock-testcontainers-nodejs";
52
28
 
53
- ## Usage
29
+ let wiremock: StartedWiremockContainer;
54
30
 
55
- ### Basic Example
31
+ beforeAll(async () => {
32
+ wiremock = await new WiremockContainer()
33
+ .withMappings("./test/__mocks__/wiremock") // mappings/ + __files/
34
+ .start();
35
+ await wiremock.waitUntilReady();
36
+ }, 60_000); // the first image pull can be slow
56
37
 
57
- 1. Record or hand-write your stubs in the standard WireMock layout, e.g.
58
- `test/__mocks__/wiremock/mappings/*.json` and
59
- `test/__mocks__/wiremock/__files/*.json`.
60
- 2. Start a `WiremockContainer` once per suite, point it at that directory and
61
- use `rpcUrl` as the base URL of the API under test.
62
- 3. Stop the container when the suite finishes.
38
+ afterEach(() => wiremock.reset()); // pristine stubs, empty journal – no restart
39
+ afterAll(() => wiremock.stop());
63
40
 
64
- ```typescript
65
- import { afterAll, beforeAll, describe, expect, it } from "vitest"; // or jest
66
- import {
67
- StartedWiremockContainer,
68
- WiremockContainer,
69
- } from "@hellaweb3/wiremock-testcontainers-nodejs";
70
-
71
- describe("Cards API", () => {
72
- let container: StartedWiremockContainer;
73
-
74
- beforeAll(async () => {
75
- container = await new WiremockContainer() // wiremock/wiremock:latest
76
- .withMappings("./test/__mocks__/wiremock") // mappings/ + __files/
77
- .start(); // waits for port 8080
78
- }, 60_000); // image pull can be slow
79
-
80
- afterAll(async () => {
81
- await container.stop();
82
- });
83
-
84
- it("serves the recorded GET /cards stub", async () => {
85
- const response = await fetch(`${container.rpcUrl}/cards`);
86
- expect(response.status).toBe(200);
87
-
88
- const body = await response.json();
89
- expect(body.items).toHaveLength(6);
90
- });
91
-
92
- it("exposes the WireMock admin API", async () => {
93
- const response = await fetch(`${container.rpcUrl}/__admin/mappings`);
94
- const { meta } = await response.json();
95
- expect(meta.total).toBe(2);
96
- });
41
+ it("serves GET /cards", async () => {
42
+ const response = await fetch(wiremock.getUrl("/cards"));
43
+
44
+ expect(response.status).toBe(200);
45
+ expect(await wiremock.countRequests({ method: "GET", url: "/cards" })).toBe(1);
97
46
  });
98
47
  ```
99
48
 
100
- `WiremockContainer` extends Testcontainers' `GenericContainer`, so every
101
- builder method is available – for example to pin an image, name the container
102
- or pass WireMock CLI flags:
49
+ ## Registering stubs
103
50
 
104
51
  ```typescript
105
- const container = await new WiremockContainer("wiremock/wiremock:3.13.0")
106
- .withName("wiremock-cards-api")
107
- .withCommand(["--verbose", "--global-response-templating"])
108
- .withMappings("./test/__mocks__/wiremock")
109
- .start();
52
+ new WiremockContainer("wiremock/wiremock:3.13.0") // default: wiremock/wiremock
53
+ .withMappings("./test/__mocks__/wiremock") // a recorded directory
54
+ .withMapping("health", "./fixtures/health.json")
55
+ .withMapping("ping", { json: { request: { url: "/ping" }, response: { status: 200, body: "pong" } } })
56
+ .withFiles("./fixtures/body.json", { file: "./logo.png", name: "img/logo.png" })
57
+ .withCommand(["--global-response-templating"]); // any Testcontainers builder method
110
58
  ```
111
59
 
112
- ### With Mappings and Files
60
+ - **`withMappings(dir)`** copies `dir/mappings/` (required) and `dir/__files/` (optional).
61
+ - **`withMapping(name, source)`** registers one stub at `mappings/<name>.json`; `name` may
62
+ contain `/`. The file may hold one stub or `{ "mappings": [...] }`.
63
+ - **`withFiles(...sources)`** registers response bodies at `__files/<name>`, the base name unless
64
+ `name` is given. All or nothing.
113
65
 
114
- WireMock uses a specific directory structure for mappings and static files. This
115
- library makes it easy to mount these into the container.
66
+ | Source | Example |
67
+ |------------------------------------|----------------------------------------------------------------|
68
+ | Path (relative to cwd) or `file:` URL | `"./fixtures/a.json"`, `new URL("./a.json", import.meta.url)` |
69
+ | `{ file }` | `{ file: "./fixtures/a.json" }` |
70
+ | `{ package, file }` – any file of an installed package | `{ package: "@acme/fixtures", file: "wiremock/a.json" }` |
71
+ | `{ module, file }` – next to a module | `{ module: import.meta.url, file: "./a.json" }` (CJS: `__dirname`) |
72
+ | `{ json }` – mappings only | an object (serialised) or a string (copied verbatim) |
116
73
 
117
- **Project structure:**
74
+ Registrations are validated before any image is pulled: sources must exist, mappings must be
75
+ WireMock stubs, and nothing may overwrite another registration or reuse a stub `id`.
118
76
 
119
- ```
120
- test/
121
- └── __mocks__/
122
- └── wiremock/
123
- ├── mappings/
124
- │ └── my-mapping.json
125
- └── __files/
126
- └── some-data.json
127
- ```
77
+ ## Extensions
128
78
 
129
- ```typescript
130
- import { WiremockContainer } from "@hellaweb3/wiremock-testcontainers-nodejs";
131
-
132
- const container = await new WiremockContainer()
133
- .withMappings("./test/__mocks__/wiremock")
134
- .start();
135
- ```
136
-
137
- ### Individual mappings and files
138
-
139
- `withMapping(name, source)` registers one stub at `mappings/<name>.json` and
140
- `withFiles(...files)` registers response bodies in `__files/` – no recorded
141
- directory required. A mapping file holds one WireMock stub, or several under
142
- `"mappings"`:
79
+ Load [WireMock extensions](https://wiremock.org/docs/extending-wiremock/) from compiled JARs – no JDK needed:
143
80
 
144
- ```json
145
- { "request": { "method": "GET", "url": "/health" }, "response": { "status": 200, "jsonBody": { "status": "UP" } } }
81
+ ```typescript
82
+ new WiremockContainer()
83
+ .withExtensionJar("./wiremock/acme.jar") // onto WireMock's class path
84
+ .withExtension("com.acme.UppercaseTransformer") // --extensions com.acme.UppercaseTransformer
85
+ .withMapping("shout", { json: { request: { url: "/shout" }, response: { body: "hi", transformers: ["uppercase"] } } });
146
86
  ```
147
87
 
148
- #### Mapping sources
88
+ | Method | Registers |
89
+ |--------------------------------------------------------|---------------------------------------------------------------------------|
90
+ | `withExtension(ext)`, `withExtensions([...])` | A fully qualified class name, or a [TypeScript extension](#typescript-extensions) |
91
+ | `withExtensionJar(jar)`, `withExtensionJars([...])` | A JAR, or every `*.jar` of a directory – any source above; `{ file, name }` renames |
92
+ | `withExtensionOptions({ scanning, verifyClassNames })` | `scanning: false` ignores JARs' `META-INF/services`; `verifyClassNames: false` skips the class check |
149
93
 
150
- **Local file** – a path relative to the working directory, or absolute:
94
+ - Class names share one `--extensions` flag, in registration order, without repeats.
95
+ - Validated before start: JARs must be readable `*.jar`s; each class must be in a registered JAR
96
+ or WireMock itself (else: *did you mean …?*); `withCommand()` must not repeat `--extensions` or
97
+ an extension's option – WireMock crashes on repeats. Start-up failures name the extensions.
98
+ - WireMock 3 runs on Java 11+, so JARs must target Java 11. Custom images must keep the official
99
+ `/var/wiremock/extensions` class path.
151
100
 
152
- ```typescript
153
- new WiremockContainer().withMapping("health", "./test/fixtures/health.json"); // → mappings/health.json
154
- ```
101
+ ### TypeScript extensions
155
102
 
156
- **`file:` URL** – e.g. relative to the current module:
103
+ Bundle class names, JARs and options; register the class or an instance:
157
104
 
158
105
  ```typescript
159
- new WiremockContainer().withMapping("health", new URL("./fixtures/health.json", import.meta.url));
160
- ```
161
-
162
- **`{ file }`** – a path or URL in object form:
106
+ import type { WireMockExtension } from "@hellaweb3/wiremock-testcontainers-nodejs";
163
107
 
164
- ```typescript
165
- new WiremockContainer().withMapping("health", { file: "./test/fixtures/health.json" });
108
+ class Uppercase implements WireMockExtension {
109
+ getName() { return "uppercase"; } // identifies it; implement at least one method below
110
+ getClassNames() { return ["com.acme.UppercaseTransformer"]; }
111
+ getJars() { return [{ module: import.meta.url, file: "./acme.jar" }]; }
112
+ getOptions() { return ["--global-response-templating"]; } // "--flag" or "--option=value"
113
+ }
114
+ new WiremockContainer().withExtension(Uppercase);
166
115
  ```
167
116
 
168
- **`{ package, file }`** – a file inside an installed npm package, e.g. shared
169
- fixtures. Resolved like `require()` from the working directory; the file does
170
- not need to be exported and may not leave the package:
117
+ The [guide for extension authors](./examples/extensions/README.md) has recipes, tests and publishing.
171
118
 
172
- ```typescript
173
- new WiremockContainer().withMapping("cards/list", { package: "@acme/api-fixtures", file: "wiremock/cards.json" }); // → mappings/cards/list.json
174
- ```
119
+ ## Test utilities
175
120
 
176
- **`{ module, file }`** – a file relative to a module, like a Java classpath
177
- resource:
121
+ `start()` resolves to a `StartedWiremockContainer`:
178
122
 
179
- ```typescript
180
- new WiremockContainer().withMapping("balance", { module: import.meta.url, file: "./__mocks__/balance.json" }); // ESM
181
- new WiremockContainer().withMapping("balance", { module: __dirname, file: "./__mocks__/balance.json" }); // CJS (or __filename)
182
- ```
123
+ | Member | Purpose |
124
+ |-------------------------------------------|-------------------------------------------------------------------------|
125
+ | `rpcUrl` | Base URL, e.g. `http://localhost:32768` |
126
+ | `getUrl(path?)` | `getUrl("cards?page=1")` → `<rpcUrl>/cards?page=1` |
127
+ | `getAdminUrl()` / `getMappingUrl()` | `<rpcUrl>/__admin` / `<rpcUrl>/__admin/mappings` (runtime stubs) |
128
+ | `isRunning()` | `true` if WireMock is healthy; never throws |
129
+ | `waitUntilReady({ timeout?, interval? })` | Polls until healthy (defaults: 30 s, every 250 ms) |
130
+ | `reset()` | Restore registered stubs and scenarios, clear the journal – no restart |
131
+ | `resetRequests()` | Clear the journal only |
132
+ | `countRequests(criteria)` | Number of matching requests since the last reset |
133
+ | `findRequests(criteria)` | The matching `RecordedRequest`s (url, method, headers, body, …) |
134
+ | `stop()`, `await using` | Stop the container |
183
135
 
184
- **`{ json }` (object)** – an inline stub, serialised as-is:
136
+ `criteria` (`WiremockRequestCriteria`) uses WireMock's [request matching](https://wiremock.org/docs/request-matching/):
185
137
 
186
138
  ```typescript
187
- new WiremockContainer().withMapping("ping", {
188
- json: { request: { method: "GET", url: "/ping" }, response: { status: 200, body: "pong" } },
139
+ const [request] = await wiremock.findRequests({
140
+ method: "POST",
141
+ urlPath: "/cards",
142
+ headers: { "Content-Type": { equalTo: "application/json" } },
189
143
  });
144
+ expect(JSON.parse(request.body ?? "")).toMatchObject({ cardType: "virtual" });
190
145
  ```
191
146
 
192
- **`{ json }` (string)** – inline JSON, copied verbatim:
147
+ Also exported: `WaitUntilReadyOptions`, the source types `WiremockMappingSource`,
148
+ `WiremockStaticFile`, `WiremockFileSource`, `WiremockPath` and `WiremockStubMapping`, and the
149
+ extension types `WireMockExtension`, `ExtensionConstructor`, `ExtensionInput`,
150
+ `WiremockExtensionJar` and `WiremockExtensionOptions`.
193
151
 
194
- ```typescript
195
- new WiremockContainer().withMapping("ping", { json: '{ "request": { "url": "/ping" }, "response": { "status": 200 } }' });
196
- ```
197
-
198
- **Several stubs in one mapping** – any source may hold `{ "mappings": [...] }`:
152
+ **Tips:** start one container per suite and `reset()` between tests, which is far faster than
153
+ restarting; raise the `beforeAll` timeout for the first image pull; assert with
154
+ `countRequests()`/`findRequests()` – they verify what actually went over the wire.
199
155
 
200
- ```typescript
201
- new WiremockContainer().withMapping("greetings", {
202
- json: {
203
- mappings: [
204
- { request: { url: "/hello" }, response: { status: 200, body: "Hello" } },
205
- { request: { url: "/bye" }, response: { status: 200, body: "Bye" } },
206
- ],
207
- },
208
- });
209
- ```
156
+ ## Without Docker (local development only)
210
157
 
211
- #### Static files
158
+ No container runtime? With Java 11+, `@hellaweb3/wiremock-testcontainers-nodejs/no-docker`
159
+ serves every `WiremockContainer` from a local WireMock JVM. Tests stay unchanged: stubs, files,
160
+ extensions and `withCommand()` flags apply as in the container.
212
161
 
213
- `withFiles()` takes the same file sources (not `{ json }`). A file keeps its
214
- base name unless an object source sets `name`; mappings reference it as
215
- `bodyFileName`:
162
+ > [!WARNING]
163
+ > Never the default, never for CI/CD: it refuses to run when CI is detected (`CI`,
164
+ > `GITHUB_ACTIONS`, …) and warns on every start. Results can differ from the Docker image; run
165
+ > with Docker before merging.
216
166
 
217
- ```typescript
218
- new WiremockContainer().withFiles(
219
- "./test/fixtures/body-cards.json", // → __files/body-cards.json
220
- { file: "./test/fixtures/logo.png", name: "images/logo.png" }, // → __files/images/logo.png
221
- { package: "@acme/api-fixtures", file: "wiremock/__files/user.json" }, // → __files/user.json
222
- );
167
+ ```bash
168
+ npm i -D wiremock # bundles the WireMock JAR (optional peer dependency)
223
169
  ```
224
170
 
225
- #### Combining them
226
-
227
- All three methods chain with each other and with every Testcontainers builder:
228
-
229
171
  ```typescript
230
- const container = await new WiremockContainer()
231
- .withMappings("./test/__mocks__/wiremock")
232
- .withMapping("health", "./test/fixtures/health.json")
233
- .withMapping("cards/list", { package: "@acme/api-fixtures", file: "wiremock/cards.json" })
234
- .withFiles({ file: "./test/fixtures/cards.json", name: "cards/body.json" })
235
- .start();
172
+ // test/no-docker.setup.ts
173
+ import { installNoDockerRuntime } from "@hellaweb3/wiremock-testcontainers-nodejs/no-docker";
174
+ installNoDockerRuntime();
175
+
176
+ // vitest.config.ts – opt-in only
177
+ setupFiles: process.env.WIREMOCK_NO_DOCKER === "1" ? ["./test/no-docker.setup.ts"] : [],
236
178
  ```
237
179
 
238
- Registrations are validated immediately, before any image is pulled: sources
239
- must exist, mappings must be WireMock stubs, and no registration may overwrite a
240
- file of another or reuse a stub `id` (WireMock would refuse to start).
241
- Registering the same file at the same path again is a no-op. Sources are only
242
- read – mappings are copied byte-for-byte as of registration.
243
-
244
- ## API
245
-
246
- ### `WiremockContainer`
247
-
248
- - `constructor(image?: string)`: Create a new container. Defaults to
249
- `wiremock/wiremock`.
250
- - `withMappings(directory: string)`: Copies `mappings/` (required) and
251
- `__files/` (optional) from the specified local directory into the container.
252
- Throws immediately if `mappings/` does not exist, so a typo fails before any
253
- image is pulled.
254
- - `withMapping(name: string, source: WiremockMappingSource)`: Registers one stub
255
- at `mappings/<name>.json` (`name` may contain `/`; `.json` is optional).
256
- - `withFiles(...files: WiremockStaticFile[])`: Registers files at
257
- `__files/<name>` – the base name unless an object source sets `name`. A call
258
- registers all files or none.
259
- - `start()`: Starts the container and returns a `StartedWiremockContainer`.
260
- Runtime failures (no daemon, image pull denied, start-up timeout, name or
261
- port conflicts) are propagated unchanged.
262
-
263
- ### `StartedWiremockContainer`
264
-
265
- - `rpcUrl`: Returns the base URL of the running WireMock instance (e.g.,
266
- `http://localhost:32768`).
267
- - `stop()`: Stops the container (inherited from Testcontainers).
268
- - `[Symbol.asyncDispose]`: `await using container = await new WiremockContainer().start();`
269
- stops the container automatically when the scope exits (TypeScript 5.2+).
270
-
271
- ## Best Practices
272
-
273
- 1. **Test Isolation**: Start a new container for each test suite (or even each
274
- test if performance allows) to ensure a clean state.
275
- 2. **Mapping Organization**: Keep your mappings organized in a dedicated
276
- directory structure that mirrors your API endpoints.
277
- 3. **Timeouts**: Docker container startup can sometimes exceed the default test
278
- runner timeout (e.g. 5 s in Jest, 10 s in Vitest). Always set a higher timeout for
279
- `beforeAll` hooks that start containers.
280
- 4. **Resource Management**: Always ensure `container.stop()` is called in an
281
- `afterAll` or `afterEach` block to prevent orphaned containers.
282
-
283
- ## Development
284
-
285
- Tooling: [Vitest](https://vitest.dev/) (tests + v8 coverage),
286
- [oxlint](https://oxc.rs/docs/guide/usage/linter) (lint), `tsc` (typecheck)
287
- and [tsdown](https://tsdown.dev/) (build – rolldown + oxc, emitting
288
- declarations natively via `isolatedDeclarations` in `tsconfig.build.json`).
289
- Node version: see `.nvmrc`.
290
-
291
- ### Scripts
292
-
293
- | Command | What it does | Needs Docker |
294
- |----------------------|--------------------------------------------------------------------------|--------------|
295
- | `pnpm build` | ESM + CJS + declarations into `dist/`, then `publint` + `attw` checks | No |
296
- | `pnpm typecheck` | `tsc --noEmit` over `src/` and `test/` | No |
297
- | `pnpm lint` | `oxlint --deny-warnings` with the rules in `.oxlintrc.json` | No |
298
- | `pnpm test:unit` | `test/unit/**` – container wrapper logic, registration API and fixture integrity checks | No |
299
- | `pnpm test:e2e` | `test/index.test.ts` – a real WireMock container serving the fixtures | **Yes** |
300
- | `pnpm test` | Both suites, no coverage (fast local loop) | **Yes** |
301
- | `pnpm test:coverage` | Both suites with coverage of `src/`, thresholds enforced | **Yes** |
302
- | `pnpm check` | `build` + `typecheck` + `lint` + `test:coverage` – the full CI gate | **Yes** |
303
-
304
- Append `--coverage` to any test script to collect coverage for that run, e.g.
305
- `pnpm test:unit --coverage`. Coverage thresholds are **95 %** for statements,
306
- branches, functions and lines; the unit suite alone currently reaches 100 %
307
- and completes in well under two seconds of test time.
308
-
309
- ### Running the E2E suite in containerised environments
310
-
311
- The E2E suite starts a real container through Testcontainers, so the test
312
- process needs a container runtime it can talk to. Without one, `pnpm test:e2e`,
313
- `pnpm test` and `pnpm check` fail in the E2E suite with:
180
+ Set `WIREMOCK_NO_DOCKER=1` from a `--no-docker` script flag – see
181
+ [`examples/scripts/test-e2e.mjs`](./examples/scripts/test-e2e.mjs), which supports
182
+ `npm test --no-docker`.
314
183
 
315
- ```
316
- Could not find a working container runtime strategy
317
- ```
184
+ | Option (`installNoDockerRuntime`) | Environment | Default |
185
+ |-----------------------------------|------------------------------|------------------------------------|
186
+ | `jar` | `WIREMOCK_JAR` | the `wiremock` package's JAR |
187
+ | `java` | `WIREMOCK_JAVA`, `JAVA_HOME` | `java` |
188
+ | `startupTimeout` | – | `60_000` ms |
189
+ | `cacheDir` (`false` disables) | – | `<tmpdir>/wiremock-testcontainers` |
190
+ | `log` | – | `process.emitWarning` |
191
+ | `env` – for the CI check and defaults | – | `process.env` |
318
192
 
319
- In that situation the Docker-free loop still covers build, types, lint and
320
- 100 % of `src/`:
193
+ - **Speed:** a C1-only JIT and a class-data archive per JAR and Java in `cacheDir`. The first run
194
+ records it (a slower `stop()`); later runs boot ~2× faster and stop instantly. `JAVA_OPTS`
195
+ override the JVM flags.
196
+ - **Differences:** WireMock listens on `127.0.0.1`; extension JARs join its class path;
197
+ `exec()`, networks and copying after start throw; leftover JVMs are killed on exit.
198
+ - **Also exported** (from `…/no-docker` and the package root): `detectCI(env?)` – the CI variable
199
+ set, if any; `isNoDockerRequested(env?)` – whether `NO_DOCKER_ENV` (`WIREMOCK_NO_DOCKER`) is `1` or `true`.
321
200
 
322
- ```bash
323
- pnpm build && pnpm typecheck && pnpm lint && pnpm test:unit --coverage
324
- ```
201
+ ## More
202
+
203
+ - [`examples/`](./examples) – a standalone project using the package via `npm link`.
204
+ - [CONTRIBUTING.md](./CONTRIBUTING.md) – scripts, CI and tests.
325
205
 
326
- Testcontainers resolves a runtime in this order; pick whichever your
327
- environment can provide:
328
-
329
- | Environment | How to enable E2E |
330
- |-----------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------|
331
- | GitHub Codespaces / VS Code Dev Containers | Use the provided `.devcontainer/devcontainer.json` – it adds the `docker-in-docker` feature, so a daemon runs inside the dev container. |
332
- | Dev container with the host socket mounted | Add the `docker-outside-of-docker` feature (or mount `/var/run/docker.sock`). Set `TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal` if port lookups fail. |
333
- | Remote or rootless daemon | `DOCKER_HOST=tcp://host:2375` (or `unix:///path/to/docker.sock`). |
334
- | Podman | `DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock` and `TESTCONTAINERS_RYUK_DISABLED=true` (or run Ryuk privileged). |
335
- | No runtime can be installed (locked-down sandbox) | [Testcontainers Cloud](https://testcontainers.com/cloud/): run the agent and export `TC_CLOUD_TOKEN`; containers start remotely, tests run locally. |
336
-
337
- Sandboxes that have **no runtime binary and no socket at all** (as is the case
338
- for some hosted dev VMs) cannot run the E2E suite; the suite states this in its
339
- header comment and `pnpm test:unit` is the supported loop there.
340
-
341
- CI (`.github/workflows/main.yml`) runs build, typecheck, lint, unit tests
342
- (with coverage) and E2E tests as separate steps on `ubuntu-latest`, which
343
- ships with Docker, for every active Node LTS line.
344
-
345
- ### Supply-chain controls
346
-
347
- - `pnpm install --frozen-lockfile` everywhere; the lockfile is the source of truth.
348
- - `pnpm-workspace.yaml` allow-lists the only packages permitted to run install
349
- scripts (`ssh2`, `cpu-features`, `protobufjs` – all transitive via
350
- `testcontainers`) and sets `minimumReleaseAge: 1440` so a version must be at
351
- least 24 h old before it can be resolved.
352
- - CI runs `pnpm audit --prod --audit-level=high` on every push and PR;
353
- Dependabot (`.github/dependabot.yml`) proposes weekly updates with a 3-day
354
- cooldown, grouping dev tooling and leaving `testcontainers` bumps individual.
355
- - `pnpm build` runs [publint](https://publint.dev/) and
356
- [Are the types wrong?](https://arethetypeswrong.github.io/) against the
357
- emitted package; `size.yml` fails a PR if the tarball exceeds 25 kB.
358
- - Releases publish with npm provenance (`--provenance`, OIDC) after a
359
- Docker-free verify step.
360
-
361
- ### Fixtures and schema-based assertions
362
-
363
- The recorded WireMock session lives in `test/__mocks__/wiremock`:
364
-
365
- - `mappings/*.json` – one stub per endpoint (`request.url`, `response.status`,
366
- `response.headers`, `response.bodyFileName`).
367
- - `__files/*.json` – the response bodies referenced by the mappings.
368
-
369
- `test/helpers/fixtures.ts` loads these from disk and declares the TypeScript
370
- shapes of every JSON payload. `test/helpers/assertions.ts` declares a
371
- `Schema<T>` per payload – stub mappings, the admin API response and each
372
- recorded body. The compiler requires a schema to cover **every** field of
373
- `T`; `expectObject` then asserts each field's shape, its recorded value and
374
- the exact key set (`partialOf` relaxes the key set for third-party shapes).
375
-
376
- The E2E suite runs a mapping-driven contract test for every file in
377
- `mappings/` (status, headers and full JSON body), so adding a new recorded
378
- mapping automatically puts it under test. Endpoint-specific suites add
379
- schema assertions, which the unit suite also runs against the fixture files
380
- themselves to catch drift early.
381
-
382
- Negative cases are hand-written stubs alongside the recorded ones:
383
-
384
- - `mapping-cards-bad-request-400.json` – `GET /cards?page=0` → 400 with a
385
- validation error envelope.
386
- - `mapping-account-balance-server-error-500.json` – `GET /account/balance`
387
- → 500 **only** when the request carries `X-Mock-Scenario: server-error`.
388
- It sets `"priority": 1` so it wins over the recorded 200 stub for the same
389
- URL; without the header the 200 stub is still served. This is the standard
390
- WireMock pattern for simulating outages on demand.
391
-
392
- Unmatched requests (unknown path, wrong method) get WireMock's default 404.
393
-
394
- `test/__mocks__/standalone` holds loose fixtures for `withMapping()` and
395
- `withFiles()`. Their unit tests need no Docker: `test/helpers/container.ts`
396
- reads the tar archive Testcontainers would upload and asserts exactly which
397
- files land where.
398
-
399
- ## Maintainers
400
-
401
- Maintained by [Hella Labs](https://hellaweb3.com/).
402
-
403
- ## License
404
-
405
- MIT
206
+ Maintained by [Hella Labs](https://hellaweb3.com/). [MIT](./LICENSE) licensed.