exisjs/testing
The exisjs/testing module provides an integrated testing framework built on Node's native runner (node:test) and assertion engine (node:assert). It includes the createTestApp synthetic HTTP client, fluent expect assertions, Jest-compatible ex spies/mocks, and unit-level mock factories (createMockRequest, createMockResponse).
Module Exports
import { // Test Runner & Lifecycle test, describe, it, before, beforeAll, after, afterAll, beforeEach, afterEach, registerCleanup,
// Assertions & Matchers expect,
// Mocks, Spies & Timers ex, mock,
// HTTP Integration Testing Client createTestApp, TestRequest,
// Unit Mocking Helpers createMockRequest, createMockResponse, createMockLogger, createMockNext, getResponseBody, getResponseHeader,} from 'exisjs/testing'
import type { TestApp, TestRequest, TestResponse, TestOptions, TestContext, TestFunction, HookFunction, MockRequestOptions, MockResponse,} from 'exisjs/testing'1. Native Test Runner API
ExisJS enhances the native Node test runner with Jest-like .each() parameterization and typed test hooks.
// Test suites and casesdescribe(name: string, fn?: TestFunction): voidtest(name: string, fn?: TestFunction): voidtest(name: string, options: TestOptions, fn?: TestFunction): voidit(name: string, fn?: TestFunction): void
// Modifierstest.only(name: string, fn?: TestFunction): voidtest.skip(name: string, fn?: TestFunction): voidtest.todo(name: string, fn?: TestFunction): void
// Lifecycle hooksbefore(fn: HookFunction): voidbeforeEach(fn: HookFunction): voidafter(fn: HookFunction): voidafterEach(fn: HookFunction): voidParametrized Tests (.each)
test.each([ [1, 2, 3], [10, 20, 30], [-5, 5, 0],])('calculates %d + %d = %d', (a, b, expected) => { expect(a + b).toBe(expected)})2. HTTP Integration Testing (createTestApp)
Instantiates an in-memory HTTP test client that dispatches synthetic requests into an ExisJS App instance without binding a network port.
function createTestApp(app: App | ExisAppDefinition | ExisConfig): TestAppTestApp Interface
| Method | Signature | Description |
|---|---|---|
get | (path: string, options?: RequestOptions): TestRequest | Initiates a synthetic GET request. |
post | (path: string, options?: RequestOptions | unknown): TestRequest | Initiates a synthetic POST request. |
put | (path: string, options?: RequestOptions | unknown): TestRequest | Initiates a synthetic PUT request. |
patch | (path: string, options?: RequestOptions | unknown): TestRequest | Initiates a synthetic PATCH request. |
delete | (path: string, options?: RequestOptions): TestRequest | Initiates a synthetic DELETE request. |
options | (path: string, options?: RequestOptions): TestRequest | Initiates an OPTIONS preflight request. |
head | (path: string, options?: RequestOptions): TestRequest | Initiates a HEAD request. |
request | (method: string, path: string, options?: RequestOptions): TestRequest | Dispatches an arbitrary HTTP method request. |
TestRequest Builder
Fluent request builder allowing header configuration, payload attachment, and response assertions:
class TestRequest implements PromiseLike<TestResponse> { send(body: unknown): this set(header: string, value: string): this expect(status: number): this expect(header: string, value: string | RegExp): this expect(body: unknown): this execute(): Promise<TestResponse>}TestResponse Interface
| Property | Type | Description |
|---|---|---|
status | number | HTTP status code returned by the route handler. |
headers | http.IncomingHttpHeaders | Dictionary of HTTP response headers. |
body | any | Parsed JSON response payload (or undefined if not JSON). |
text | string | Raw UTF-8 string content of the response payload. |
Integration Test Example
import { describe, it, expect, createTestApp } from 'exisjs/testing'import server from '../src/http/server'
describe('Users API', () => { const client = createTestApp(server)
it('creates and returns a user payload', async () => { const res = await client .post('/users', { name: 'Alice', email: '[email protected]' }) .set('Authorization', 'Bearer token_123') .expect(201) .expect('content-type', /application\/json/)
expect(res.body).toMatchObject({ success: true, data: { name: 'Alice', email: '[email protected]', }, }) })})3. Assertion Engine (expect)
The expect(actual) function returns a rich matcher engine with negative chaining via .not:
Equality & Truthiness
| Matcher | Description |
|---|---|
toBe(value) | Strict referential equality (===). |
toEqual(value) | Deep equality check across objects and arrays. |
toStrictEqual(value) | Deep strict equality checking prototypes and undefined keys. |
toMatchObject(subset) | Verifies that the target object contains the specified subset of properties. |
toBeTruthy() | Asserts truthy value. |
toBeFalsy() | Asserts falsy value. |
toBeNull() | Asserts null. |
toBeUndefined() | Asserts undefined. |
toBeDefined() | Asserts not undefined. |
Numeric Matchers
| Matcher | Description |
|---|---|
toBeGreaterThan(n) | Asserts actual > n. |
toBeGreaterThanOrEqual(n) | Asserts actual >= n. |
toBeLessThan(n) | Asserts actual < n. |
toBeLessThanOrEqual(n) | Asserts actual <= n. |
toBeCloseTo(n, precision?) | Asserts floating point approximation. |
Strings, Arrays & Errors
| Matcher | Description |
|---|---|
toContain(item) | Checks if a string contains a substring or array contains an element. |
toMatch(regex) | Asserts string matches regular expression. |
toHaveLength(length) | Asserts .length property matches. |
toThrow(error?) | Asserts that a function throws an error matching the predicate. |
toBeInstanceOf(constructor) | Asserts actual instanceof constructor. |
Mock Assertions
| Matcher | Description |
|---|---|
toHaveBeenCalled() | Asserts the mock was called at least once. |
toHaveBeenCalledTimes(count) | Asserts the mock was invoked an exact number of times. |
toHaveBeenCalledWith(...args) | Asserts the mock was called with matching arguments. |
4. Spies, Mocks & Timers (ex)
Jest-compatible API wrapping Node's native mocking subsystem:
// Function & Method Spiesconst fn = ex.fn((x: number) => x * 2)const spy = ex.spyOn(userService, 'findById')
// Mock Implementationsfn.mockReturnValue(42)fn.mockResolvedValue({ id: '1' })fn.mockRejectedValue(new Error('Failed'))fn.mockImplementation((name) => `Hello ${name}`)fn.mockClear()fn.mockRestore()
// Timer Manipulationex.setSystemTime(new Date('2026-01-01T00:00:00Z'))ex.advanceTimersByTime(5000)
// Module Mockingex.mock('./mail-client', () => ({ sendMail: ex.fn().mockResolvedValue(true),}))5. Unit-Level Mocking Helpers
For unit testing individual middleware, guards, or filters without booting an entire server:
import { createMockRequest, createMockResponse, createMockNext, getResponseBody, getResponseHeader,} from 'exisjs/testing'
const req = createMockRequest({ method: 'POST', url: '/api/v1/checkout', headers: { 'x-tenant-id': 'tenant_123' }, body: { amount: 5000 },})
const res = createMockResponse()const next = createMockNext()
await authGuard(req, res, next)
expect(next.called).toBe(true)