# Tests prove behavior through supplied services

> For agents: start with the [agent guide](https://ratstack.sh/llms.txt). Every page is Markdown by default; add `Accept: text/html` for HTML.

New on /news: [tla-ts 0.0.9 adds TLC's -continue](/news).

A service is a named interface. A Layer describes how to build a service and supply its dependencies.
A Scope owns cleanup. A capability is a named action; a projection converts it into an interface.

Name the behavior that lacks proof before adding a test.
Supply test services through Layers, return an Effect, and let `@effect/vitest` own its execution.

## Test behavior independently of the implementation

A test that repeats the implementation cannot detect the same mistake in both places.
A snapshot that only records a changed file does not prove a domain rule.
If the existing suite already covers the reported behavior, fix the cause without adding a duplicate test.

Our projection tests execute the produced CLI command, HTTP route, MCP tool, RPC call, or sandbox call.
They check results and declared failures. Returning a projection object does not establish correct behavior.

## Run an Effect through supplied services

`it.effect` runs the Effect returned by the test. `it.layer` supplies services and their dependencies to a block of tests.
The file-inspection integration test uses real platform services and a scoped temporary file:

packages/core/test/file-inspector.test.ts at 65e9465f38092e24486392f22c08f45d61230c20; lines 5-5,24-34; highlighted none.

```typescript
import { FileInspector } from "../src/index.js";
it.layer(TestLayer)("FileInspector", (test) => {
  test.effect("reads a file through the Effect filesystem", () =>
    Effect.gen(function* readsThroughFileSystem() {
      const file = yield* writeTempFile("notes.txt", "one two\nthree\n");
      const inspector = yield* FileInspector;

      const stats = yield* inspector.inspect(file);

      expect(stats).toMatchObject({ bytes: 14, lines: 2, words: 3 });
    })
  );
```

Share a Layer when tests need a common expensive resource.
Do not assume its mutable state resets between tests in the same block.
Use fresh construction or explicit reset when the behavior needs isolation.

`TestClock` controls Effect time for sleeps and retries. Use it instead of waiting for wall-clock delays in a behavior test.
Use `it.live` for an integration test that needs live runtime services.

## Choose the smallest proof that fits the rule

- **One interface between components**: An integration example with supplied services
- **A rule over many inputs**: `it.prop` or `it.effect.prop`
- **State that depends on earlier actions**: A model checked after generated commands
- **UI behaviour, timers, and races**: A [Foldkit Scene](/lore/ui-behaviour-is-a-scene)

Generated inputs do not create the assertion. The test still needs an independent statement of correct behavior.

`Arbitrary.schema` derives input generators from Schema.
Pure properties use `it.prop`; properties returning Effects use `it.effect.prop`.
The core suite generates names and checks that validation trims them before applying the length rule.

## Keep the assertion independent

The name-validation property checks the length rule after trimming each generated string:

packages/core/test/join-identity.test.ts at f1e24f7ee9f9b5f4a06574cb8c1646de001e4a3f; lines 101-120; highlighted none.

```typescript
it.effect.prop(
  "names trim before the nonempty 100-character bound",
  { input: Arbitrary.schema(Schema.String) },
  ({ input }) =>
    Effect.gen(function* names() {
      const trimmed = input.trim();

      const exit = yield* Schema.decodeEffect(ApplicantName)(input).pipe(
        Effect.exit
      );

      expect(Exit.isSuccess(exit)).toBe(
        trimmed.length > 0 && trimmed.length <= 100
      );

      if (Exit.isSuccess(exit)) {
        expect(exit.value).toBe(trimmed);
      }
    })
);
```

The assertion does not call `ApplicantName` to decide whether a name is valid.
It states the accepted length directly and checks the trimmed result.

When the sensitive-query test was first written, deleting `token` from the filter did not fail a test that reused that filter.
An independent sensitive-key property closed the gap.
[Property-based testing](/lore/property-based-testing) records that planted violation and the smaller counterexample.

## Generate histories when order matters

The inspection lifecycle keeps its first terminal outcome after later commands.
The model generates action sequences and checks each step:

packages/core/test/inspect-machine.model.test.ts at f1e24f7ee9f9b5f4a06574cb8c1646de001e4a3f; lines 23-25,214-219; highlighted none.

```typescript
const Command = Schema.Literals(["succeed", "fail", "probe"]);

const commands = Arbitrary.array(Arbitrary.schema(Command), { maxLength: 30 });
  it.effect.prop(
    "real actor preserves generated input, typed outcomes and terminal immutability",
    { generated: commands, scenario: Scenario },
    ({ generated, scenario }) => replayActor(scenario, generated),
    { arbitrary: { runs: 200 } }
  );
```

[Model-based testing](/lore/model-based-testing) explains how to check the system against a model after each step.
[Agents explore, tests remember](/lore/agents-explore-tests-remember) explains how exploration can expose a behavior gap worth making repeatable.

## Common wrong approach

Plant a violation in the behavior it claims to protect. Observe failure, then restore the implementation and observe success.

Do not rewrite the test's expected result to match a broken implementation.
Keep the counterexample or missing assertion that explains the behavior gap.

## Effect idiom and house rule

- Effect-aware tests supply runtime services, own scopes, and support controlled time.
- In rat-stack, no manual Effect runtime in tests. Prefer properties and models; prove new properties with planted violations.
- Live-provider behavior needs separate proof for each cartridge.

See also: [properties](/lore/property-based-testing), [models](/lore/model-based-testing), [scenes](/lore/ui-behaviour-is-a-scene), [exploration](/lore/agents-explore-tests-remember), and [the fence](/lore/the-fence).

Read [why dependency injection matters](/lore/dependency-injection-is-the-reason-to-choose-effect).

## Sources

1. [Effect contributors. 2026. Effect Vitest integration.](https://github.com/Effect-TS/effect/blob/67ba4e46a11ccda0b6761578bfd22c04ae00167d/packages/vitest/src/index.ts)
   Effect-TS. it.effect, it.layer, it.prop, and effectful properties expose Effect-aware test execution. Accessed 2026-10-06.

2. [Effect contributors. 2026. Writing Effect tests.](https://github.com/Effect-TS/effect/blob/67ba4e46a11ccda0b6761578bfd22c04ae00167d/ai-docs/src/09_testing/10_effect-tests.ts)
   Effect-TS. TestClock controls Effect time, and effect properties can use Schema-based inputs. Accessed 2026-10-06.

3. [Effect contributors. 2026. Layer isolation tests.](https://github.com/Effect-TS/effect/blob/67ba4e46a11ccda0b6761578bfd22c04ae00167d/packages/vitest/test/isolation.test.ts)
   Effect-TS. Separate it.layer blocks construct isolated service state. A shared block is not a per-test reset. Accessed 2026-10-06.

4. [Effect contributors. 2026. Arbitrary. Effect 4.0.0.](https://github.com/Effect-TS/effect/blob/67ba4e46a11ccda0b6761578bfd22c04ae00167d/packages/effect/src/Arbitrary.ts)
   Effect-TS. Schema-derived generators, generated command arrays, and shrinking support properties and history models. Accessed 2026-10-06.

5. [rat-stack contributors. 2026. Core behavior tests.](https://github.com/joelhooks/rat-stack/tree/f1e24f7ee9f9b5f4a06574cb8c1646de001e4a3f/packages/core/test)
   rat-stack. An independent name-validation property and generated inspection histories exercise domain behavior. Accessed 2026-10-06.

6. [rat-stack contributors. 2026. Agent instructions.](https://github.com/joelhooks/rat-stack/blob/65e9465f38092e24486392f22c08f45d61230c20/AGENTS.md)
   rat-stack. Testing policy rejects tautologies and change detectors, prefers properties/models, and requires planted violations for new properties. Accessed 2026-10-06.

7. [Langton, Kit. 2026. Testing. Effect Solutions.](https://github.com/kitlangton/effect-solutions/blob/09f82e6c5c928e7232cd32daf04d7c6a830b63f7/packages/website/docs/08-testing.md)
   Kit Langton. Topic framing for Effect-aware tests and replaceable services; the property/model standard is rat-stack's policy. Accessed 2026-10-06.

## Lore on this page

- [Scopes release resources when work ends](/lore/scopes-own-resources)
- [Cartridges](/lore/cartridges)

## Linked from

- A service captures its construction dependencies → Build a service with its dependencies, then expose methods that callers can use without rebuilding the graph. → [Read page](https://ratstack.sh/lore/services-capture-dependencies)

- Agent guide → An Effect stack so pure (aspirational) Kit Langton will blush. → [Read page](https://ratstack.sh/llms.txt)

- Agents explore, tests remember → Let agent users explore scenarios; turn a good bug reproduction into a repeatable test. → [Read page](https://ratstack.sh/lore/agents-explore-tests-remember)

- Change log → What changed in the files served here, newest first. → [Read page](https://ratstack.sh/log)

- Code snippets quote a pinned source → Shipped design brief: pinned Git objects, typed diagnostics, and one build-only snippet model for both views. → [Read page](https://ratstack.sh/lore/code-snippets)

- Configuration is a supplied, validated dependency → Parse configuration when a Layer builds, reject invalid values, and supply controlled settings in tests. → [Read page](https://ratstack.sh/lore/configuration-is-a-dependency)

- Dependency injection is the reason to choose Effect → Effect tracks service requirements in types and supplies replaceable implementations through Layers. → [Read page](https://ratstack.sh/lore/dependency-injection-is-the-reason-to-choose-effect)

- Effect basics → An Effect describes work, expected failures, and required services before a runtime executes it. → [Read page](https://ratstack.sh/lore/effect-basics)

- Full agent guide → An Effect stack so pure (aspirational) Kit Langton will blush. → [Read page](https://ratstack.sh/llms-full.txt)

- Layer composition makes dependencies explicit → Supply a service's dependencies, choose which services remain visible, and reuse Layer identity within a build. → [Read page](https://ratstack.sh/lore/layers-make-dependencies-explicit)

- learn-rat-stack → Trace a capability through Effect, XState, five projections, the CLI, and Alchemy. Try hosted search and read. → [Read page](https://ratstack.sh/skills/learn-rat-stack)

- Model-based testing → Describe what a system should do as a small model, generate action sequences, and check the real system against the model. → [Read page](https://ratstack.sh/lore/model-based-testing)

- No comments → Code comments are banned because names, types, tests, and boundaries should carry the meaning. → [Read page](https://ratstack.sh/lore/no-comments)

- Property-based testing → State a rule that must hold for every input, then let a generator hunt for the smallest input that breaks it. → [Read page](https://ratstack.sh/lore/property-based-testing)

- Rat Stack lore | rat-stack → Short, source-grounded notes on the ideas and decisions behind rat-stack. → [Read page](https://ratstack.sh/lore)

- Run Effect at the integration boundary → Build the runtime where a calling framework enters, reuse its services, and dispose it when that integration ends. → [Read page](https://ratstack.sh/lore/run-effect-at-the-boundary)

- Schemas define the boundary → Define accepted data with Schema, decode unknown input before dispatch, and encode results through the declared contract. → [Read page](https://ratstack.sh/lore/schemas-define-the-boundary)

- Source change history (log.md) → The generated change log as Markdown. → [Read page](https://ratstack.sh/log.md)

- Structure an Effect codebase by domain → Start with folders for application work, services and Layers in the same file, and startup code that supplies implementations. → [Read page](https://ratstack.sh/lore/structure-effect-by-domain)

- test-behaviour → Pick the test shape that proves a behaviour. Use when you ask "how do I test this?", add a test, test a domain rule, a lifecycle, a retry, a session, a UI interaction, a timer, or a race, or review a test that might be tautological. → [Read page](https://ratstack.sh/skills/test-behaviour)

- The fence → The fence uses compiler checks, lint rules, and hooks to reject unsafe shortcuts. → [Read page](https://ratstack.sh/lore/the-fence)

- UI behaviour is a scene → A Foldkit Scene drives update, view, and subscriptions with input and time as plain data, then asserts on what the view shows. → [Read page](https://ratstack.sh/lore/ui-behaviour-is-a-scene)

## Unlinked mentions

- [Agent rules for rat-stack (AGENTS.md)](https://ratstack.sh/AGENTS.md) → Tautological tests considered harmful.
