How Sounding works
Before we talk about the runtime, it helps to define Sounding's core word:
A trial is one named behavior check proving something true about your app in a real Sails runtime.
Sounding keeps the familiar test() API, but it uses trial as the conceptual word because the framework is built around product behaviors, worlds, actors, and the conditions a system must safely pass before you ship.
If you want the full concept model, read Trials.
Sounding is built around a simple idea:
Boot a real Sails app, create a deterministic world for the current trial, then give the test the right tools for the layer it is exercising.
The runtime in five steps
1. Sounding boots your app
When a trial starts, Sounding boots your Sails app in test mode and exposes its public testing runtime on sails.sounding.
That runtime is where the rest of the system hangs together:
- app lifecycle
- world loading
- auth helpers
- mailbox capture
- request and browser orchestration
2. Sounding resolves a datastore strategy
Sounding should respect normal Sails test configuration before it does anything clever.
By default, Sounding manages a temporary sails-sqlite datastore for the run or the worker and stores those files under .tmp/db.
That gives us a clean, Sails-native story:
- managed by default for zero-ceremony installation
- inherit when the app already has a solid test setup it needs to preserve
- external when a team wants to bring its own dedicated test datastore
- keep tests written against helpers, Waterline, requests, and browser flows either way
3. Sounding loads a world
This is where Sounding should feel different from a generic test runner.
It does not just create rows. It loads a named business situation from tests/factories and tests/scenarios.
That means:
- factories define the primitive record shapes
- traits express meaningful variants like
publisherorsubscriber - scenarios compose those pieces into a situation like
issue-accessorpublisher-editor - the resolved world is what the trial actually uses
A world includes:
- actors such as guests, publishers, subscribers, or admins
- records like issues, subscriptions, teams, or unlocks
- the relationships between them
- the current business situation the trial cares about
A good world gives the trial readable handles like current.users.subscriber and current.issues.gatedIssue, not vague keys like user1 or record2. It should feel like a product situation, not a fixture dump.
4. Sounding furnishes the right test surface
Every trial starts from the same calm context.
One important part of that surface is transport.
Sounding should keep one request story in user land while choosing the right transport underneath:
- virtual requests through
sails.request()when we want fast, app-aware trials - real HTTP requests when parity with the live HTTP stack matters more
That gives us one stable API without pretending every kind of request behaves the same way.
The transport choice should resolve calmly in this order:
- a per-call override like
get('/health', { transport: 'http' }) - a per-trial override like
test('...', { transport: 'http' }, ...) - the default from
config/sounding.js
And when a trial wants to stay explicit without changing every alias, it should be able to scope a client with sails.sounding.request.using('http').
From there, the furnished context stays simple:
sailsis the real app runtimesails.helpers,sails.models, andsails.configstay canonicalsails.soundingexposes worlds, mailbox capture, and request orchestration- top-level aliases like
get(),post(), andvisit()exist for the most common flows visit()understands Inertia-specific request headers like partial reloads without introducing a second testing API- browser-capable trials can additionally destructure
pagewhen the browser really matters
5. Sounding reports what mattered
When a trial fails, the output should help you understand the failure quickly:
- which world was loaded
- which actor was in play
- which request or browser step failed
- which part of app state was relevant
Why the hook matters
Because Sounding is a Sails hook, it can speak the native language of a Sails app without asking you to bolt on a second mental model.
It can understand and expose:
- helpers
- actions
- policies
- routes
- Waterline models
- sessions and auth
- Inertia responses
- jobs and realtime over time
That is the difference between tests that merely run and a testing story that actually feels at home in Sails.
What a trial context means
A trial is the behavior being proved. A trial context is the single object Sounding passes into test() so a behavior can be proved inside a real app runtime.
It is the environment the current trial lives inside.
That context should feel calm and familiar:
sailsis the real app runtimesails.helpers,sails.models,sails.config, andsails.hooksstay canonicalsails.soundingexposes Sounding-specific capabilities like the world engine, mailbox, and request orchestration- top-level aliases like
get(),post(), andvisit()exist only as ergonomic shortcuts expectis always available for assertions
So when we say “trial context,” we do not mean a second fake app abstraction. We mean: the real Sails runtime, plus a small set of testing capabilities shaped around it.
The design patterns behind Sounding
Sounding should stay grounded in a few simple patterns:
sailsis the real runtime object in every trialsails.soundingis where Sounding-specific capabilities live- top-level aliases like
get()andpost()are conveniences, not a separate system - worlds describe business situations, not random fixtures
- calm defaults come first; heavier tools should only appear when a trial really needs them
That is how Sounding stays elegant instead of turning into a bag of special cases.