Skip to content

8. Test the graph

Goal: test Effect programs, their Layers and the Craft boundary without mocking the whole application.

Test an Effect service with a partial mock

mockEffectService provides a Layer and makes every unstubbed member fail loudly if the test accidentally uses it:

typescript
import { Effect } from 'effect';
import { mockEffectService } from '@craft-ts/effect';

const register = {
  AccessPolicyService: mockEffectService(AccessPolicyService, {
    decide: () => Effect.succeed(expectedDecision),
  }),
};

For production-like tests, provide the real Layer. For focused tests, stub only the selected members and let UnstubbedEffectMember expose an unexpected read.

Test the Craft service or component by register

Craft tests still use a register derived from the Craft dependency graph:

typescript
const { sut } = await setupCraftServiceTestingByRegister(
  AccessDecisionService,
  {
    AccessPolicyService: mockEffectService(AccessPolicyService, {
      decide: () => Effect.succeed(expectedDecision),
    }),
  },
);

The exact register also includes regular Craft services, 'real', 'notReached' or provideX() entries when those nodes are reachable. Effect service mocks cover the Effect side; the Craft register proves the full graph is accounted for.

Test the bridge and adapters

Install and dispose the bridge per test suite:

typescript
let dispose: () => void;

beforeEach(() => {
  dispose = installCraftEffectBridge();
});

afterEach(() => {
  dispose();
});

Cover at least one example of each channel: a typed failure becomes a Craft exception, Effect.die rejects as a technical error, and aborting the owning resource interrupts the Effect.

Architecture checks

The static graph includes the Effect backend automatically when analyzeDependencyGraph runs. It exposes typed nodes for effect-service, effect-operation and effect-layer, together with requires-service, provided-by-layer and composes-layer relations. You can therefore write rules against Effect concepts just as you do against Craft nodes:

typescript
const effectServices = graph.nodes('effect-service');
const effectOperations = graph.nodes('effect-operation');
const effectLayers = graph.nodes('effect-layer');
const serviceRequirements = graph.edges('requires-service');

The built-in checks can then be kept beside the app's architecture tests:

typescript
assertCraftEffectNoNetwork(graph.graph);
assertCraftEffectNoImperativeSync(graph.graph);
assertDeclarativeArchitecture(graph.graph);

For a project-specific invariant, inspect these typed nodes and relations in a custom assertion and fail the architecture test when the rule is violated. If you need to add concepts that the built-in Effect backend does not model, use the DependencyGraphNodeRegistry and DependencyGraphCollector. The Effect graph is already collected; no extra collector is needed just to apply rules to its services, operations or Layers.

What you gained

Tests that mirror the real Craft and Effect graphs: real composition by default, narrow mocks at the boundary, and architecture rules for the invariants that types alone cannot keep armed.