Stone.jsDocs
Paradigm

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

terminalbash
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.

tests/tasks.test.tsts
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

APITypeDescription
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) => eventBuild an event: { method, url, body, headers, query }.
response.statusCodenumberThe response status.
response.getContent()() => unknownThe 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.

tests/prune.test.tsts
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')

Stone.js

Your app exists in every runtime. Until you run it.

An open-source project by Stone Foundation
Created by Mr. Stone (Evens Pierre)