apiunit
v5.0.0
Published
Unit testing framework for API
Downloads
8,004
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:
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.
{
"data": [
{
"key1": {
"values": [1,2,3]
},
"key2": {
"values": [4,5,6]
}
},
{
"key1": {
"values": [1,2,3]
},
"key2": {
"values": [4,5,6]
}
}
]
}Example request:
Example response:
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 noCONNECTran first (using this request'ssec-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 SENDs — 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.bodywith 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 contributesundefined. 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 underothers. - apiunit-ws-done-grace: after a
donemarker, how long (ms) to keep reading for trailing frames a server may emit after the stream completes (e.g. configurapi'ssessionNameUpdated). Default 1000. Set to 0 to stop immediately ondone. - APIUNIT-RETRY: for
.wsthis is the per-message idle read timeout in milliseconds —.wsrequests are never re-sent (a streamed send is not idempotent), so unlike.req/.msgthe header does not retry; it specifies how long to wait for the next frame before ending the stream.truemaps 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:
- streams — frames carrying a stream-id header (
apiunit-ws-stream-id-header, defaultx-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.
othersis an array, soapiunit-ignore-array-orderand 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):
Example .wsresp for a configurapi stream (assembled answer under streams, discrete events under others; regexes supported):
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).
