Skip to main content

Write your first test

The problem​

You have run the sample suite. Now add a test that creates a project and checks the response: one request, two checks, one filtered run.

The sample already configures the application and its clients, so you write only the test.

Do it​

1. Add the file​

Create MyFirstJourney.cs in samples/Northstar.ProtoTest/ with the code below. The callouts explain the setup, request and checks.

MyFirstJourney.cs9 notes
1namespace Northstar.ProtoTest;
2
3using System.Net;
4using global::ProtoTest.Core;
5using global::ProtoTest.Http;
6using global::ProtoTest.NUnit;
7using global::ProtoTest.Rest;
8using global::ProtoTest.SampleApp.Contracts;
9
10[Application(NorthstarTargets.Api)]
11[NorthstarMember]
12public sealed class MyFirstJourney
13{
14[ProtoTest]
15[SignedInAs]
16public async Task CreatingAProjectReturnsIt()
17{
18var name = $"first-{Proto.Context.TestId}";
19using var created = await Proto.Context.Rest()
20.Body(new CreateProjectRequest(name))
21.PostAsync("/api/v1/projects");
22
23created
24.Should.HaveHttpStatus(HttpStatusCode.Created)
25.Should.MatchShape(new { name, status = ProjectStatuses.Active });
26}
27}
  1. Use the package namespace

    global:: starts namespace lookup at the root. This avoids confusing the ProtoTest packages with the enclosing Northstar.ProtoTest namespace.

  2. Select the application

    Application selects the API configured in the sample setup class.

  3. Prepare a tenant and authentication

    NorthstarMember groups tenant creation with an authenticator that sends the member token. The tenant provisioner also registers cleanup for teardown.

  4. Run through the NUnit adapter

    This NUnit attribute marks the method as a test and wraps its lifecycle. The adapter creates and completes the test context, including setup and teardown.

  5. Declare the test identity

    SignedInAs declares who the test acts as. With no role specified, the sample authenticator uses the tenant owner token.

  6. Include the test id in the name

    The default generator gives each test in this host a different id. That does not guarantee unique names across separate runs.

  7. Retrieve the REST client

    Rest() returns the client from this test context. The setup class supplies its address, so this test names no port.

  8. Check the HTTP status

    HaveHttpStatus checks for 201 Created and records the comparison in the trace.

  9. Check the response fields

    MatchShape checks name and status in the JSON body. A differing value produces a message with the JSON path, expected value and actual value.

2. Run it alone​

From the repository root:

dotnet test samples/Northstar.ProtoTest --filter "FullyQualifiedName~MyFirstJourney"

The filter selects your new test. You should see one passed test and no failures. The run also writes a trace, the archive of recorded work from the run.

Find it under samples/Northstar.ProtoTest/, in the same folder as in lesson 1:

bin/Debug/net8.0/TestResults/prototest-{runId}.prototrace

3. Run it again​

Run the same command a second time. You should see one passed test again and a new trace file.

The project name contains Proto.Context.TestId, which usually differs between runs but is not guaranteed to.

The sample isolates project data in a tenant created for each test. Its tenant provisioner registers cleanup that removes the tenant and its projects at teardown. Repeatability depends on that isolation and cleanup, not on the project name changing.

What happened​

The example uses four parts of the configured sample:

  • Context. The NUnit adapter creates a test context, the object behind Proto.Context. It holds this test's clients, state and attachments.
  • Client. Proto.Context.Rest() retrieves the REST client for the selected application. The client knows the API address, so the test supplies a path.
  • Data. NorthstarMember prepares a tenant and registers its cleanup. [SignedInAs] declares the identity that the sample authenticator uses for the request.
  • Checks. The status check expects HTTP 201 Created. The shape check compares name and status, allowing other response fields. Both checks record their comparisons.

You wrote the request and its expected result. The existing setup class and attributes supply the application, client, identity and data cleanup.

Check yourself​

Why can this test run again without depending on the project name changing?

Verify
Run the filter twice. Both runs should pass. Find the attribute in the example that prepares the tenant and its cleanup.

Remember​

  • This test sends one request and checks its status and two response fields.
  • The client takes its address from the run, so the test names no port.
  • The sample's tenant setup and cleanup keep test data separate. A generated name alone does not.

Go deeper​