Skip to main content

Keep state per test and clean it up

The state drill from Level 0 reads a project id that no test in the run created (prj_1, a fixed id that belongs to no tenant), and the request 404s. That drill is one of the four in the failure tour. The fix creates its own state and reads only that. This lesson reads the fix in the trace, including what the run owns and removes.

Level 3, lesson 3About 8 minutes
By the end
  • Provision state in setup under a name that carries the test id.
  • Read only the state the test created.
  • Find the cleanup and the release that prove the state was removed.
Before you start
  • Wait for readiness, not for time (lesson 2).
  • Nothing installed. The archives are on this site.

The scenario​

A test that reads state it did not create depends on whatever ran before it. The fix for the state question is short: create the data the test needs, read it, and let teardown remove it.

In the sample the unit of isolation is the tenant. An attribute provisions one per test, the test works inside it, and teardown deletes it. The project the test creates lives inside that tenant and goes with it.

Where the tenant comes from​

The attribute that provisions it uses a name from the test:

NorthstarAttributes.cs3 notes
1public override async Task BeforeTestAsync(ProtoExecutionContext context)
2{
3var tenant = await context.Data()
4.For<ProvisionTenantRequest>()
5.With(request => request.Name, context.UniqueName("northstar"))
6.With(request => request.PlanId, PlanId)
7.CreateAsync<TenantResponse>();
8context.SetContext(new NorthstarOrganizationContext(
9tenant.Tenant,
10tenant.OrganizationId,
11tenant.OwnerEmail,
12tenant.OwnerToken,
13tenant.ApiBaseUrl));
14}
  1. Provision through the data client

    The request says what the test needs; the registered provisioner decides how it is created.

  2. Name it from the test id

    UniqueName turns "northstar" into "northstar-<test id>", so no other test can share the record.

  3. Hand the result to the test

    The provisioned tenant lands in the execution context, where the test and its clients read it.

From samples/Northstar.ProtoTest/NorthstarAttributes.cs. The provisioner returns the tenant with a disposer, which is what makes the run own it.

The journey that reads it​

EachTenantSeesOnlyItsOwnProjects creates a project and lists the projects its tenant can see:

FailureDrills.cs2 notes
1var name = $"own-{Proto.Context.TestId}";
2var project = await Proto.Context.Data().CreateProjectAsync(name);
3
4using var response = await Proto.Context.Rest().GetAsync("/api/v1/projects");
5var page = response
6.Should.HaveHttpStatus(HttpStatusCode.OK)
7.ReadRequired<CursorPage<ProjectResponse>>();
  1. Create with the test id

    The name carries TestId, so the record belongs to this test even though the project name itself is ordinary.

  2. Read only the own tenant

    The list call runs inside the provisioned tenant, so it returns the one project this test created.

From samples/Northstar.ProtoTest/FailureDrills.cs. The drill next to it read prj_1, a fixed id that belonged to no test in the run.

The list holds one project, and it is the one the test just created. The drill next to it read prj_1, a fixed id that belonged to no test in the run, and the check failed on the 404 the application answered.

The two layers to read​

From l0-state-fix.prototrace:

LayerEntryWhat it proves
Setupdata.create ProvisionTenantRequest, 149.6 ms, then data.provision, 144.6 msthe tenant exists before the body runs, named northstar-553135000001 in this recording
Setupattribute.before SignedInAs, then its auth.user.sign-in eventthe member acts inside that tenant
Executiondata.create CreateProjectRequest, 100.2 msthe project is created under the test's own tenant
Executionhttp.request REST GET /api/v1/projects, 72.6 ms, HTTP 200the read the check judged
Teardowndata.cleanup TenantResponse, 9.0 ms, and resource.release of data:TenantResponse:1the owned state is removed, and the release is recorded

The value list tells the same story: the tenant item records owned: true, and the project is a value the test read. One cleanup covers both.

What a leak would look like​

If the cleanup were missing, the teardown layer would hold no data.cleanup and no release for the tenant, and the record would stay in the store. The run prefix changes per run, so the next run provisions a fresh tenant. The leak still grows the store. It collides when a suite fixes its run prefix or points at a shared environment. The trace is where the leak is visible before that happens.

Checkpoint​

The trace lists the tenant as an owned value and records its cleanup and release. The project the test created is not released on its own. Where does it go?

Verify
Download l0-state-fix.prototrace, open it in the viewer, and compare the setup and teardown layers.

What you learned​

  • A test provisions the state it reads, under a name built from its own test id.
  • The owned resource is the tenant; the data inside it is removed with it.
  • Teardown records each cleanup and release, so a missing one is visible in the same place.

Keep exploring​