Skip to main content

Give each test its own state

The problem​

A test that assumes a record exists can depend on earlier tests or leftover data. The sample's state drill requests the project prj_1 without creating it. The application answers 404, while the test expects 200. The drill is one of four in the failure tour.

The fix creates a project in the test's own tenant, then checks that its list contains that project. You can follow the source excerpts and saved trace without running the sample.

Do it​

1. Give the test its own tenant​

In this sample, a tenant groups one customer's data. NorthstarTenantAttribute creates one during test setup. NorthstarMember includes that attribute, so the state drill and its fix both receive a tenant:

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 registered provisioner decides how the tenant is created.

  2. Include the test id in the name

    UniqueName returns "northstar-<test id>". A name is not an access boundary.

  3. Keep the tenant details in context

    The ids and owner credentials stay with this test.

From samples/Northstar.ProtoTest/NorthstarAttributes.cs.

2. Create the data inside it, and read it back​

EachTenantSeesOnlyItsOwnProjects creates a project, then 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 in this test's tenant

    The provisioner uses the signed-in member's tenant.

  2. Read the same tenant

    The member's token makes the application list only this tenant's projects.

From samples/Northstar.ProtoTest/FailureDrills.cs. The drill next to it read prj_1.

The omitted NUnit assertions check TotalCount == 1, then compare the returned project's id and name with the values created above. Those checks prove that the list contains the expected project in this run.

3. Compare the drill and the fix​

The drillThe fix
Before the reada tenant exists, but the test creates no projectCreate · CreateProjectRequest, in the test's tenant
The callGET /api/v1/projects/prj_1, HTTP 404GET /api/v1/projects, HTTP 200
The checkexpected 200, got 404the list holds one project, the one this test created

4. Find the creation in the trace​

Download l0-state-fix.prototrace and open it in the viewer. Select EachTenantSeesOnlyItsOwnProjects. The Steps tab shows its operations phase by phase: Setup before the test body (folded into one line, so open it), then Execution for the body. The first word of each kind, such as data or http, says what sort of step it is:

LayerEntryWhat it shows
Setupdata.create ProvisionTenantRequest, 149.6 msthe tenant northstar-553135000001 exists before the body runs
Setupattribute.before SignedInAs, then auth.user.sign-inthe test declares its identity
Executiondata.create CreateProjectRequest, 100.2 msthe project, in the test's own tenant
Executionhttp.request REST GET /api/v1/projects, 72.6 ms, HTTP 200the read the check judged

Open the response attachment to see totalCount: 1 and the project named own-553135000001.

What happened​

The test arranged its own tenant and project. Its authenticated request selected that tenant, and the application limited the list to that tenant's projects. The name helped identify the record, but did not provide the access boundary.

This holds under parallel runs too: each test keeps its own tenant credentials in its own context.

Cleanup is a separate step. Northstar's tenant provisioner returns a cleanup that deletes the tenant, and ProtoTest registers it as a test-owned resource. Lesson 6, What a test leaves behind, follows it.

Check yourself​

Why does EachTenantSeesOnlyItsOwnProjects find exactly one project, even when other tests run at the same time?

Verify
In l0-state-fix.prototrace, read the setup entry that provisions the tenant and the execution entry that creates the project.

Remember​

  • Arrange the data a test needs and check the returned identity. Do not assume a fixed id already exists.
  • Use separate tenant state and credentials for isolation. Naming and cleanup are separate responsibilities.
  • The setup layer of the trace shows what was provisioned. The execution layer shows what the test did with it.

Go deeper​