fluentrest-ts
Version:
A lightweight, fluent TypeScript API testing library inspired by Java's RestAssured. Built on top of Axios, JSONPath, and Joi for powerful request handling and response validation.
446 lines (327 loc) β’ 10.4 kB
Markdown
# fluentrest-ts
A lightweight, fluent TypeScript API testing library inspired by Java's RestAssured. Built on top of Axios, JSONPath, and Joi for powerful request handling and response validation.
## π¦ Installation
```bash
npm install fluentrest-ts
```
## π Quick Start
```ts
import { fluentRest } from "fluentrest-ts";
const response = await fluentRest()
.setBaseUrl("https://jsonplaceholder.typicode.com")
.givenHeader("Accept", "application/json")
.whenGet("/posts/1");
response
.thenExpectStatus(200)
.thenExpectBody("$.id", 1);
```
> βοΈ Note: `thenExpectX` methods require the result of a request (you must `await` the `whenX()` call).
## π Fluent API Overview
| Method | Description |
|--------|-------------|
| `setBaseUrl(url)` | Sets the base URL |
| `setTimeout(ms)` | Overrides timeout |
| `setLogLevel(level)` | Sets log verbosity ("debug" \| "info" \| "none") |
| `enableFileLogging(bool)` | Enables or disables file-based logging |
| `givenHeader(key, value)` | Adds a request header |
| `givenQueryParam(key, value)` | Adds a query parameter |
| `givenBody(obj)` | Sets JSON request body (Serialized strings or types) / Attaches multipart form-data or files|
| `debug()` | Prints current config to console |
| `getSnapshot()` | Returns snapshot of current request config |
| `whenGet(url)` | Sends a GET request |
| `whenPost(url)` | Sends a POST request |
| `whenPut(url)` | Sends a PUT request |
| `whenPatch(url)` | Sends a PATCH request |
| `whenDelete(url)` | Sends a DELETE request |
| `whenHead(url)` | Sends a HEAD request |
| `whenOptions(url)` | Sends an OPTIONS request |
| `sendAndExpect(method, url, fn, overrides?)` | Sends a request and runs assertions |
```
> βοΈ Note: `givenBody` method is now unified with multipart form-data.
## β
Response Validator API
After each request, you receive a `ResponseValidator` object with the following methods:
| Method | Description |
|--------|-------------|
| `thenExpectStatus(code)` | Assert HTTP status code |
| `thenExpectBody(path, val)` | Assert JSONPath value |
| `thenExpectBodyContains(fragment)` | Assert body contains key-values |
| `thenValidateBody(schema)` | Joi schema validation |
| `thenExpectHeader(k, v)` | Assert response header |
| `thenExtract(path)` | Extract JSONPath value |
| `catchAndLog(fn)` | Wrap and log assertion failures |
| `getResponse()` | Raw Axios response |
| `getRequestConfig()` | Request config used |
| `wasFailure()` | True if request failed |
| `runAssertions()` | run multiple expectations (assertions) against a response object |
## π§ͺ Examples by Method
```
### POST with JSON and Assertions
```ts
await fluentRest()
.setBaseUrl("https://jsonplaceholder.typicode.com")
.givenBody({ title: "foo", body: "bar", userId: 1 })
.whenPost("/posts")
.thenExpectStatus(201)
.thenExpectBody("$.title", "foo");
```
### GET with Query Params
```ts
await fluentRest()
.setBaseUrl("https://jsonplaceholder.typicode.com")
.givenQueryParam("userId", "1")
.whenGet("/posts")
.thenExpectStatus(200);
```
### PUT with Header
```ts
await fluentRest()
.setBaseUrl("https://jsonplaceholder.typicode.com")
.givenHeader("Authorization", "Bearer token")
.givenBody({ title: "updated title" })
.whenPut("/posts/1")
.thenExpectStatus(200);
```
### PATCH and Body Fragment Check
```ts
await fluentRest()
.setBaseUrl("https://jsonplaceholder.typicode.com")
.givenBody({ title: "patched" })
.whenPatch("/posts/1")
.thenExpectBodyContains({ title: "patched" });
```
### DELETE
```ts
await fluentRest()
.setBaseUrl("https://jsonplaceholder.typicode.com")
.whenDelete("/posts/1")
.thenExpectStatus(200);
```
### HEAD
```ts
await fluentRest()
.setBaseUrl("https://jsonplaceholder.typicode.com")
.whenHead("/posts/1")
.thenExpectStatus(200);
```
### OPTIONS
```ts
await fluentRest()
.setBaseUrl("https://jsonplaceholder.typicode.com")
.whenOptions("/posts")
.thenExpectStatus(204);
```
## π§© Logging
```ts
await fluentRest()
.enableFileLogging(true)
.setLogLevel("debug")
.whenGet("/posts/1")
.thenExpectStatus(200);
```
**Log levels:**
- `"debug"` β log everything
- `"info"` β request + response
- `"none"` β silence
Logs are written to `logs/restassured-<pid>.log` by default unless overridden via `configureDefaults`.
## π οΈ Global Defaults
```ts
import { configureDefaults } from "fluentrest-ts";
configureDefaults({
timeout: 15000,
logLevel: "info",
logFilePath: "logs/custom.log",
});
```
You may also use `.env` variables:
```
RA_TIMEOUT=20000
RA_LOG_LEVEL=debug
RA_LOG_FILE=logs/run.log
RA_BASE_URL=https://jsonplaceholder.typicode.com
```
> Ensure `.env` is loaded **before** any `fluentrest-ts` import.
## π€ Combined Send & Assert (Compact Tests)
```ts
await fluentRest()
.setBaseUrl("https://jsonplaceholder.typicode.com")
.sendAndExpect("post", "/posts", res => {
res.thenExpectStatus(201).thenExpectBody("$.title", "foo");
}, {
headers: { "Content-Type": "application/json" },
body: { title: "foo", body: "bar", userId: 1 }
});
```
## π runAssertions - FluentRest Soft Assertion Utility
```
- Run multiple assertions on a single response.
- Continue executing all checks even if one fails.
- Aggregate failures and throw a combined error.
- Use with `.catchAndLog()` for cleaner test output and logging.
```
```ts
const res = await fluentRest().whenGet('/posts/1');
await res.runAssertions([
r => r.thenExpectStatus(404), // will fail
r => r.thenExpectBody('$.title', 'wrong') // will also fail
]).catchAndLog(err => {
expect(err.message).toContain('Multiple assertion failures');
});
```
## π‘οΈ Assertion Wrapper: `catchAndLog()`
Use `catchAndLog()` to wrap custom assertions or logic. If it throws, the error will be logged with context.
```ts
const response = await fluentRest()
.setBaseUrl("https://jsonplaceholder.typicode.com")
.whenGet("/posts/1");
response.catchAndLog(() => {
const body = response.thenExtract("$.body");
if (!body || body.length < 10) {
throw new Error("Body is unexpectedly short");
}
});
```
This is useful for edge-case checks or combining your own logic with the libraryβs logging system.
## π§ͺ Extract and Reuse Response Data
```ts
const post = await fluentRest()
.setBaseUrl("https://jsonplaceholder.typicode.com")
.givenBody({ title: "foo", body: "bar", userId: 1 })
.whenPost("/posts");
const id = post.thenExtract("$.id");
await fluentRest()
.setBaseUrl("https://jsonplaceholder.typicode.com")
.whenGet(`/posts/${id}`)
.thenExpectStatus(200);
```
## π§ͺ Joi Schema Validation Example
Use Joi to validate the full response body structure:
```ts
import Joi from "joi";
const schema = Joi.object({
id: Joi.number().required(),
title: Joi.string().required(),
body: Joi.string().required(),
userId: Joi.number().required()
});
await fluentRest()
.setBaseUrl("https://jsonplaceholder.typicode.com")
.whenGet("/posts/1")
.thenValidateBody(schema);
```
## π Debugging
To inspect the request before sending:
```ts
fluentRest()
.setBaseUrl("https://jsonplaceholder.typicode.com")
.givenBody({ name: "debug" })
.debug();
```
## π§© Request Snapshot Debugging
You can print or retrieve a snapshot of the full request configuration:
```ts
const builder = fluentRest()
.setBaseUrl("https://jsonplaceholder.typicode.com")
.givenHeader("X-Debug", "true")
.givenQueryParam("debug", "1");
builder.debug(); // Console output
const snapshot = builder.getSnapshot();
console.log("Snapshot method:", snapshot.method);
```
This is useful for troubleshooting test cases, comparing requests, or snapshot testing.
## π Proxy Support
### π§ Global Proxy Configuration
Apply a proxy to all requests by default:
```ts
configureDefaults({
proxy: {
host: 'proxy.example.com',
port: 8080,
auth: {
username: 'user',
password: 'pass'
}
}
});
```
### π Per-Request Proxy Override
Override the global proxy using `.setProxy()`:
```ts
const response = await fluentRest()
.setProxy({
host: 'custom.proxy.com',
port: 3128,
auth: {
username: 'customUser',
password: 'customPass'
}
})
.whenGet('/posts/1');
```
### π Disabling Proxy for a Specific Request
Disable proxy even if one is globally configured:
```ts
const response = await fluentRest()
.clearProxy()
.whenGet('/health');
```
### π Proxy Resolution Order
1. `setProxy(...)` β per-request override
2. `configureDefaults(...)` β global default
3. No proxy β if `.clearProxy()` is used
### π¦ Proxy Object Format
The proxy object must match Axios's format:
```ts
{
host: string;
port: number;
auth?: {
username: string;
password: string;
};
protocol?: 'http' | 'https';
}
```
## π Utilities and Tools
- `configureDefaults()` β Global config via code
- `.env` support β Set RA_TIMEOUT, RA_LOG_LEVEL, etc.
- `debug()` β Print live config to terminal
- `getSnapshot()` β Inspect request config object
- `thenExtract(path)` β Pull specific data from response
- `catchAndLog(fn)` β Wrap and log assertion errors with context
## π§± Designed For
- TypeScript-first testing
- Frameworks like Playwright / Vitest / Jest / Mocha
- CI/CD environments
- Readable and compact API test syntax
## π¦ Dependencies
| Package | Purpose |
|----------------|-----------------------------------|
| Axios | HTTP client |
| JSONPath-Plus | JSON extraction from response |
| Joi | Schema validation |
| Form-Data | Multipart/form-data support |
| Chalk | Terminal logging colors |
## π License
MIT β Use, extend, and enjoy!