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