Extensions
Testing
A test is only worth its resemblance to production. @stone-js/testing boots the actual application in memory and sends it real intentions through the same kernel production uses, so you assert on behaviour a caller would see, not on the shape of your mocks.
#Install
npm i -D @stone-js/testing#Behaviour over mocks
The principle
Tests built from mocks verify that your code calls your mocks. They pass while the system is broken and break while it is fine. A test that drives the real boundary verifies what actually happens.
In Stone.js
createTestApp boots your real app on an in-memory adapter and returns a client. app.send(makeIncomingHttpEvent(...)) dispatches an event through the full kernel, and you assert on the returned response. No HTTP, no network, full fidelity.
import { createTestApp, makeIncomingHttpEvent } from '@stone-js/testing'
import { TaskController } from '../app/TaskController'
import { TaskService } from '../app/TaskService'
it('creates a task', async () => {
const app = await createTestApp({ modules: [TaskController, TaskService] })
const response = await app.send(makeIncomingHttpEvent({
method: 'POST',
url: '/tasks',
body: { title: 'Ship the docs' }
}))
expect(response.statusCode).toBe(201)
expect(response.getContent()).toMatchObject({ title: 'Ship the docs' })
})Boot the real app. Send a real intention. Assert on the real response.
#The harness API
| API | Type | Description |
|---|---|---|
| createTestApp(options?) | (opts) => Promise<TestClient> | Boot the app in memory. options.modules lists decorated classes and/or blueprints; options.blueprint merges a base blueprint. |
| app.send(event) | (event) => Promise<Response> | Dispatch an event through the full kernel. |
| makeIncomingHttpEvent(opts) | (opts) => event | Build an event: { method, url, body, headers, query }. |
| response.statusCode | number | The response status. |
| response.getContent() | () => unknown | The response body. |
#Every context, one harness
Because the harness dispatches intentions through the kernel, the same test covers the behaviour whether the app will finally run on Node, on the edge, or as agent tools. You test the domain once; the contexts do not change what it does.
HTTP is not the only cause. makeIncomingHttpEvent builds an HTTP intention; makeIncomingEvent builds a generic one, so the same app.send()exercises a CLI command or an agent tool call, no server and no argv parsing required.
import { createTestApp, makeIncomingEvent } from '@stone-js/testing'
const app = await createTestApp({ modules: [PruneCommand, TaskService] })
const res = await app.send(makeIncomingEvent({ name: 'tasks:prune', days: 30 }))
expect(res.getContent()).toContain('Pruned')