# apiunit

> Unit testing framework for API

Latest version **5.0.0** (published 2026-09-29) · MIT license · 0 weekly downloads

## Install

```sh
npm install apiunit
pnpm add apiunit
yarn add apiunit
bun add apiunit
```

Provides the command `apiunit`.

## Health

**Score 70/100 (B)** — status: active.

Positive: has types; no vulnerabilities; has provenance; recently updated; high maintenance score; high quality score.

Warnings: low downloads; no esm support.

## Facts

| | |
|---|---|
| Version | 5.0.0 |
| Published | 2026-09-29 |
| First published | 2017-07-05 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | CommonJS |
| Dependencies | 14 |
| Unpacked size | 129.3 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Provenance | attested (Unknown) |
| Author | Nithiwat Kampanya |
| Maintainers | mappies |

## Links

- npm: https://www.npmjs.com/package/apiunit
- Repository: https://gitlab.com/mappies/apiunit
- Issues: https://gitlab.com/mappies/apiunit/issues
- npm.io page: https://npm.io/package/apiunit

## Dependencies (14)

- [ws](https://npm.io/package/ws.md) ~8.21.3
- [colors](https://npm.io/package/colors.md) 1.4.0
- [jsonpath](https://npm.io/package/jsonpath.md) ^1.1.1
- [commander](https://npm.io/package/commander.md) ^2.20.3
- [deep-diff](https://npm.io/package/deep-diff.md) ^1.0.0
- [http-file](https://npm.io/package/http-file.md) ^1.0.3
- [tolerance](https://npm.io/package/tolerance.md) ^1.0.0
- [sqs-consumer](https://npm.io/package/sqs-consumer.md) ^14.0.0
- [sqs-producer](https://npm.io/package/sqs-producer.md) ^8.0.0
- [sync-request](https://npm.io/package/sync-request.md) ^6.1.0
- [fast-xml-parser](https://npm.io/package/fast-xml-parser.md) ^5.3.5
- [uglifyjs-folder](https://npm.io/package/uglifyjs-folder.md) ^3.0.0
- [@aws-sdk/client-sqs](https://npm.io/package/@aws-sdk/client-sqs.md) ^3.552.0
- [typescript-collections](https://npm.io/package/typescript-collections.md) ^1.3.3

## Recent versions

- 5.0.0 (latest) — 2026-09-29
- 4.1.0 — 2026-09-21
- 4.0.1 — 2026-02-18
- 4.0.0 — 2026-02-12
- 3.3.1 — 2026-02-12
- 3.3.0 — 2026-02-11
- 3.2.4 — 2025-10-14
- 3.2.3 — 2025-09-30
- 3.2.2 — 2025-09-30
- 3.2.1 — 2024-04-11
- 3.2.0 — 2024-04-11
- 3.1.0 — 2023-07-26
- 2.8.0 — 2023-05-17
- 2.7.0 — 2023-03-02
- 2.6.1 — 2023-01-26
- … 71 more at https://npm.io/package/apiunit/versions

## README

# Apiunit

Apiunit is an awesome api unit testing framework! 
It allows creation of http requests and responses to create unit tests. 

# Commands

--input (-i): path to directory containing inputs (/requests is default)
--diff (-d): path to directory containing expected output (/responses is default)
--parameter (-p): path to the parameter .json file
--prefix (-x): prefix of request(input) files to run
--cmd (-c): command to be run between scenarios
--scn (-s): The scenario or comma separated scenarios to run.

## Parameter file example
Parameter files should be JSON format. Here is an example:
<pre>
{
   "PARAM1":"VALUE1",
   "PARAM2":"VALUE2",
   "settings":{
      "scenarios":{
         "scn1":{
            "PARAM1":"scn1",
            "PARAM2":"scn1"
         },
         "scn2":{
            "PARAM1":"scn2",
            "PARAM2":"scn2"
         }
      },
      "groups":{
         "assets/testSuite":[
            "scn1",
            "scn2"
         ]
      }
   }
}
</pre>

In this example we are running two scenarios named **scn1** and **scn2** on the folder(group) of "assets/testSuite". In each scenario, the **PARAM1** and **PARAM2** variables will be replaced with the values in the scenarios.

## Request / Response file examples
Request files should end with **.req**, **.msg**, or **.ws** extension. Response files end with **.resp** (for `.req`), or **.wsresp** (for `.ws`). Every request should have a matching response with the **same** name minus extension. Response files can also use regular expressions to match expected responses.

Request files also allow the parameter **APIUNIT-RETRY** which can be set to any value, but default is **15000ms**. **APIUNIT-RETRY** will retry the request until it matches the associated response or fails within the given timeframe.

Response files also allow the header **apiunit-ignore-array-order** which use a JSONPath expression to allow arrays to be unordered. Use `path1,path2` where the value is a comma separated list of expressions to allow. Any array can be represented with `$..`, or a more explicit path can be specified. For example, targeting `values` in only `key1` in the following json structure would look like `data[*].key1.values`.
```json
{
   "data": [
      {
         "key1": {
            "values": [1,2,3]
         },
         "key2": {
            "values": [4,5,6]
         }
      },
      {
         "key1": {
            "values": [1,2,3]
         },
         "key2": {
            "values": [4,5,6]
         }
      }
   ]
}
```

Example request:
<pre>
GET ${some-domain-name-from-parameters}/some-path/${some-id-from-previous-test-or-parameters}
</pre>

Example response:
<pre>
200
content-type: application/json

{
	"maybe-an-id":".+",
	"some-field":"some-value",
	"another-field":"another-value"
}
</pre>

## WebSocket tests (.ws)
WebSocket requests use the **.ws** extension and pair with a **.wsresp** expected-response file.

All `.ws` files within the same suite (folder) share a single WebSocket connection per endpoint URL, so a `CONNECT` followed by one or more `SEND` requests reuse the same socket. Because the flow is stateful, `.ws` files in a suite are executed in **filename order** — name them accordingly (e.g. `01_connect.ws`, `02_prompt.ws`). The shared connection(s) are closed when the suite finishes.

### Verbs
- **CONNECT `<wss-url>`** — Opens (or reuses) the connection and asserts the handshake. The response carries the handshake status code (**101** on success, e.g. **403** when the server rejects the connection), so authentication can be tested explicitly. No body is read.
- **SEND `<wss-url>`** — Reuses the suite connection, **auto-connecting on demand** if no `CONNECT` ran first (using this request's `sec-websocket-protocol`), then sends the request body and reads incoming frames until stream completion (or the read times out), bucketing them into `{ streams, others }` — see "Chunk merge semantics" below.

A WebSocket subprotocol is negotiated only at connect time and cannot change on an open socket, so the token is fixed by whichever request first opens the connection to a URL. Reusing the connection with the same `sec-websocket-protocol` (or none) is fine; supplying a *different* protocol for a URL that is already connected is an error (it would otherwise be silently ignored). The normal flow — `CONNECT` with a token, then token-less `SEND`s — is unaffected. To assert an authentication rejection (e.g. 403), use `CONNECT` (which returns the handshake status), rather than relying on SEND's auto-connect (which surfaces a rejected handshake as a request error).

### Authentication and headers
- **sec-websocket-protocol**: sent as the WebSocket subprotocol(s) on the handshake (comma-separated for multiple). This is the generic way to pass an auth token.
- **apiunit-ws-merge-path**: the path within each frame to the value that is bucketed/merged. Default is **`payload.body`** with a strict fallback: if a frame lacks that path, the whole frame is used for that frame. When set explicitly there is **no fallback** — a frame lacking the path contributes `undefined`. Use `.` (or `$`) to select the whole frame. See "Chunk merge semantics" below.
- **apiunit-ws-stream-id-header**: the frame header whose value identifies a stream (default **`x-stream-id`**, the configurapi convention). Frames carrying this header are merged per id; frames without it are collected under `others`.
- **apiunit-ws-done-grace**: after a `done` marker, how long (ms) to keep reading for trailing frames a server may emit *after* the stream completes (e.g. configurapi's `sessionNameUpdated`). Default **1000**. Set to **0** to stop immediately on `done`.
- **APIUNIT-RETRY**: for `.ws` this is the **per-message idle read timeout** in milliseconds — `.ws` requests are never re-sent (a streamed send is not idempotent), so unlike `.req`/`.msg` the header does not retry; it specifies how long to wait for the next frame before ending the stream. `true` maps to **15000**, a number is used as-is, and absent falls back to **10000**. This is the maximum wait *between* frames, not a total budget for the whole stream — the clock resets each time a frame arrives, so a steady stream never times out while a stalled one does.

### Stream termination
A stream ends when a received frame carries the marker `x-stream: done` — checked first in a top-level `headers` object on the frame (the configurapi convention), and, if absent there, anywhere within the frame body (a generic fallback).

A `done` marker ends the *stream*, but a server may emit trailing frames *after* it (e.g. configurapi sends a `sessionNameUpdated` push once the answer is complete). So after `done`, apiunit keeps reading for a short **grace window** (`apiunit-ws-done-grace`, default 1000ms) to drain those trailing frames, then stops. Set the grace to `0` to stop immediately on `done`.

If no `done` ever arrives, the read ends on the **per-message idle timeout** (`APIUNIT-RETRY`): if no new frame arrives within that window, whatever has been received so far is bucketed and returned — a partial response (status code **0** if nothing arrived at all). This is not an error: the partial result is diffed against the `.wsresp` like any response, so an incomplete stream surfaces as a body-assertion mismatch rather than an exception.

### Chunk merge semantics
A streamed response arrives as a **non-deterministic** number of frames, and it interleaves two different things: the streamed content (many chunks of one value) and discrete events (status, notifications). apiunit reconciles both by bucketing the frames into a single deterministic object:

<pre>
{
   "streams": { "&lt;stream-id&gt;": &lt;merged value&gt;, ... },
   "others":  [ &lt;value&gt;, ... ]
}
</pre>

- **streams** — frames carrying a stream-id header (`apiunit-ws-stream-id-header`, default `x-stream-id`) are grouped by that id and **merged** into one value per id. Because merging collapses any number of chunks into one value, this is independent of the chunk count. Multiple concurrent stream ids are supported.
- **others** — every frame *without* a stream id is **collected in arrival order** (not merged), so discrete events stay individually assertable. `others` is an array, so `apiunit-ignore-array-order` and per-element regexes apply.

From each frame the bucketed value is extracted at **`payload.body`** by default, falling back to the whole frame when that path is absent (strict: only when the key is missing, not when it is empty). Set `apiunit-ws-merge-path` to override (no fallback when set); `.` or `$` selects the whole frame.

Within a stream bucket, values are merged in arrival order:
- **string** + **string** → concatenation (`"Hel"` + `"lo"` → `"Hello"`)
- **number** + **number** → addition (`2` + `4` → `6`)
- **boolean** + **boolean** → logical AND (`true` + `false` → `false`)
- **array** + **array** → concatenation (`["a","b"]` + `["x"]` → `["a","b","x"]`)
- **object** + **object** → deep merge by key
- a key present in only one frame is taken as-is
- **type mismatch** → both values are stringified and concatenated (`1` + `true` → `"1true"`)

The asserted **status code** is that of the last non-`done` frame (the terminal `done` frame's status, e.g. 204, is ignored). The asserted **response headers** are the headers of the **last frame received**, verbatim; `content-type` defaults to `application/json` only when the last frame does not set one, so the bucketed body is diffed as JSON. As with `.req`/`.msg`, a `.wsresp` only asserts the headers it cares about — extra response headers are allowed.

Example `.ws` request (`02_prompt.ws`):
<pre>
SEND wss://${WS_URL}/prod
sec-websocket-protocol: ${ACCESS_TOKEN}
message-id: ${some-id}

{"name":"prompt","payload":{"sessionId":"${SESSION}","message":{"type":"user","content":"hi"}}}
</pre>

Example `.wsresp` for a configurapi stream (assembled answer under `streams`, discrete events under `others`; regexes supported):
<pre>
200
content-type: application/json

{
   "streams": { ".+": "Hello! How can I help you today?" },
   "others": [
      "",
      { "type": "status", "message": ".+" },
      { "type": "sessionNameUpdated", "title": ".+" }
   ]
}
</pre>

## Example Execution
To run a simple apiunit task just run the command "node ../node_modules/.bin/apiunit -i specs/requests -d specs/responses -p parameters.json -c \"npm run add-some-data-or-something\"".

## Migration of v3 to v4
In `4.0.0` apiunit changed from using (json-path)[https://www.npmjs.com/package/json-path?activeTab=readme] to (jsonpath)[https://www.npmjs.com/package/jsonpath?activeTab=readme]. This is a breaking change, however, we attempted to support for the simple use case of `/propertyName` returning was preserved. In any other case the query needs to be re-written in the new supported notation (see jsonpath documentation).

---
_Source: https://npm.io/package/apiunit · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
