Know when the host starts and stops
The problem
Nothing in the sample's tests calls Setup.Configure, yet every test finds a database, an application and a trace sink ready. Something starts them before the first test and something stops them after the last.
You need to know who does that, and why a test can never add to the composition.
Do it
1. Find the setup class
The setup class configures the host for a test project. Open samples/Northstar.ProtoTest/Setup.cs. It is short at the top because its base class does the work:
1[SetUpFixture]2public sealed class Setup : ProtoTestAssembly3{4protected override void Configure(IProtoHostBuilder builder)5{6var configuration = LoadConfiguration();7var run = NorthstarRun.From(configuration);8run.PrepareOwnedStore();910ConfigureInfrastructure(builder, run);11ConfigureApplications(builder, run);12ConfigureDomain(builder, run);13// ... tracing, sinks, the run gate and messaging14}15}
- NUnit's once-per-assembly hook
The base class carries the attribute and the setup and teardown methods. Deriving from ProtoTestAssembly is the whole registration.
- You only write Configure
It receives the builder. A test body never sees the builder, so the composition stays one readable list.
samples/Northstar.ProtoTest/Setup.cs, trimmed to the declaration and the three composition calls.2. See what the base class does with it
The base class starts the host once before any test and stops it once after the last. This is NUnit's:
1[SetUpFixture]2public abstract class ProtoTestAssembly3: ProtoTestAssemblyHost<ProtoTestAssembly>, IProtoTestAssemblyHost<ProtoTestAssembly>4{5[OneTimeSetUp]6public Task GlobalSetUp() => StartAsync(Configure);78[OneTimeTearDown]9public Task GlobalTearDown() => StopAsync();1011protected abstract void Configure(IProtoHostBuilder builder);12}
- Start once
Before any test, the base builds the host from Configure and starts it.
- Stop once
After the last test, the base stops the host and disposes it.
src/ProtoTest.NUnit/ProtoTestAssembly.cs. The other runner packages hook into their own runner's start and end, then take the same path below.Under that, every runner takes the same three steps:
1var builder = new ProtoHostBuilder();2configure(builder);3var host = builder.Build();4await host.StartAsync().ConfigureAwait(false);
- Configure composes
Your Configure adds applications, integrations, sinks, hooks and gates to one builder.
- Build is the last step
It creates the host. After it, any registration throws: "The ProtoHostBuilder has already built a ProtoHost; configure a new builder instead."
- Start opens the run
Run hooks run, capabilities are recorded and infrastructure starts. Only then do tests start.
src/ProtoTest.Core/ProtoTestHostLifetime.cs, the shared start path behind every runner.3. Look for the end of the run in a trace
Open l1-first-journey.prototrace in the viewer. The host writes its own work in the run layer: the part of the trace that belongs to no single test. At the end, three entries release run resources after the test's teardown:
| Entry | What it releases |
|---|---|
Release · messaging:broker | the run's messaging adapter, in memory in this recording |
Release · readiness:application:Northstar web | the readiness check for the web application |
Release · application:loopback:Northstar web | the listener the browser journey follows |
What happened
The runner called your Configure once, built the host, started it, ran the tests, then stopped it. The test never owned the messaging adapter or the listener, so no test teardown could release them. The host did, once, at the end.
Build is the last step on purpose. A registration after it would change a run that already started, so the builder throws. Each test still gets its own context and clients. Those are per-test state, not registrations.
In l2-broker-skip.prototrace the only selected test skipped. The host still started, recorded its capabilities and released the same three resources.
Check yourself
The first journey's trace lists seven capabilities and three run resources. Who releases the three, and when, relative to the test?
The host releases them, in the run layer, after the test's teardown. The test never owned them, so no test teardown could have released them.
Remember
- One host per test project: built from
Configure, started before the first test, stopped after the last. - A test cannot add to the host. Registrations after
Build()throw. - The run layer of a trace records the host's own work: capabilities, resources and releases.
Go deeper
- Host and lifecycle: the full sequence of run hooks, gates, reports, resources and the archive, and how a failing stop is reported.
- Next: Add and remove an integration.