The Stage & lifecycle
The Stage is the runtime a test runs against. It holds the services a test resolves — the real
system-under-test plus its (real or mocked) collaborators — and it’s what Arrange, Act and Inspect
pull from. Everything else in Mokkit sits on top of it.
Compose once, enter per test
Section titled “Compose once, enter per test”A Stage comes in two steps:
TestStageSetup.Create(...builders)composes your containers — this is the expensive part, and you do it once.setup.EnterStage()returns a fresh, isolatedTestStage— you do this once per test, and dispose it afterwards.
// Once — usually in a class/collection fixture.var setup = await TestStageSetup.Create( new BagContainerBuilder() .AddInstance(email) .AddInstance(new SignupService(email)));
// Per test.var stage = setup.EnterStage();// ... arrange / act / inspect ...stage.Dispose();Each EnterStage() opens its own scope, so tests are isolated: scoped services are created per stage and
disposed when the stage is disposed. Nothing leaks between tests.
What a Stage gives you
Section titled “What a Stage gives you”stage.Arrange(); // → ITestArrange — start the setup chainstage.Act(); // → ITestAct — start the act chainstage.Inspect(); // → ITestInspect — start the observe chain
stage.Execute<TService>(svc => ...); // resolve one service and run itstage.ExecuteAsync<TService, TOut>(svc => ...); // resolve, run, return a resultExecute/ExecuteAsync come in 1-to-4-service arities, so a step can pull several collaborators at once.
Your vocabulary verbs are thin wrappers over exactly these calls.
Wiring it to your test framework
Section titled “Wiring it to your test framework”Mokkit is framework-agnostic — the Stage is composed in whatever “run once” hook your runner offers and entered in its “per test” hook. The pattern is identical across xUnit, NUnit and MSTest; only the fixture attributes differ.
// xUnit — the composition is an IClassFixture (built once); each test enters a fresh stage.public abstract class BaseUnitTest<TFixture> : IClassFixture<TFixture>, IDisposable where TFixture : BaseStageFixture{ protected BaseUnitTest(TFixture fixture) => Stage = fixture.EnterStage();
protected TestStage Stage { get; }
protected ITestArrange Arrange => Stage.Arrange(); protected ITestAct Act => Stage.Act(); protected ITestInspect Inspect => Stage.Inspect();
public void Dispose() => Stage.Dispose();}Exposing Arrange / Act / Inspect as properties on a base fixture is what lets a test body read as
await Arrange.… / await Act.… / await Inspect.… with no ceremony.
One composition per system-under-test
Section titled “One composition per system-under-test”Because containers are built once per composition, a service is either the real thing or a mock within a given Stage — not both. So a type that is the system-under-test in one test but a dependency in another needs its own fixture:
One fixture per SUT: its real type, plus mocks for that type’s direct dependencies.
This is a feature, not a limitation — it keeps each test’s composition small and obvious. The project structure page shows how to organise fixtures per feature.
- Containers & the mock→DI bridge — what goes into
Create, and how mocks reach the real service. - Captures: Capture vs Trapture — how artifacts thread from one phase to the next.