Skip to main content

Move the test clock

A billing period takes a month. This test closes one in about 230 ms. The application runs on the test clock, and the test moves it.

Level 3, lesson 1About 8 minutes
By the end
  • Move a test clock instead of waiting on real time.
  • Explain how the in-process application reads the same clock.
  • Find the clock event and the period close in the trace.
Before you start

The scenario​

A subscription renews every billing period. A test for the due window has to place the application a period later, and a test that sleeps through that time is slow on a good day and wrong on a bad one.

The sample hosts the API in-process, and that server hands the application the test's clock. ClockJourney asks the application where the period ends, moves the clock just past that instant, and checks the invoice the application issued.

One clock, two readers​

The composition starts the API in-process. That server is what carries the test clock into the application:

Setup.cs3 notes
1builder.AddApplication(NorthstarTargets.Api, app =>
2{
3if (run.RunsLocalApplications)
4{
5// The in-process server is what carries the test clock into the application.
6app.AddAspNetCoreServer<NorthstarProgram>(configureWebHost: webHost =>
7ConfigureHostedApplication(webHost, run));
8}
  1. The API application

    Tests select it with [Application(NorthstarTargets.Api)].

  2. Only when the run hosts the application

    A run pointed at a deployed address does not start a server, and it cannot move that application time.

  3. The bridge

    The in-process server replaces the application's time provider with the run's, so the application computes every stamp and period from the test clock.

From samples/Northstar.ProtoTest/Setup.cs. The suite also removes the domain's own time provider in its test-side composition, so the bridge wins and the application keeps the test clock.

The test then moves that clock and reads what the application did with it.

The journey​

ClockJourney.cs3 notes
1using var subscription = await Proto.Context.Rest().GetAsync("/api/v1/subscription");
2var current = subscription
3.Should.HaveHttpStatus(HttpStatusCode.OK)
4.ReadRequired<SubscriptionResponse>();
5
6// Move the test clock past the period end; the application reads the same clock.
7Proto.Context.Clock.Advance(
8current.CurrentPeriodEndUtc - Proto.Context.Clock.GetUtcNow() + TimeSpan.FromSeconds(1));
9
10using var invoices = await Proto.Context.Rest()
11.GetAsync("/api/v1/invoices", new { status = InvoiceStatuses.Open });
12var invoice = invoices
13.Should.HaveHttpStatus(HttpStatusCode.OK)
14.ReadRequired<CursorPage<InvoiceResponse>>()
15.Items
16.Single();
  1. Ask the application for the window

    The period end is the application's value, read over the composed client.

  2. Move the test clock

    The delta is the time left in the period plus one second, so the clock lands just past the boundary.

  3. Read what the move produced

    The application issued the invoice when the period closed, and the test reads the open one.

From samples/Northstar.ProtoTest/ClockJourney.cs. The test asserts the invoice stamp and its line afterwards with ordinary NUnit assertions.

What the trace recorded​

From the archive:

EntryWhat it shows
clock.advance on test.execution, "Clock advanced by 30:0:00:01"the delta, from the test side
http.request REST GET /api/v1/subscription, 141.9 ms, HTTP 200the period the test read
Northstar.Domain invoice.issue, reported by the applicationthe application closed the period
http.request REST GET /api/v1/invoices, 68.4 ms, HTTP 200the invoice the test read
test.execution, 233.7 msthe whole journey

The clock advance is an event on the test execution, not a request. Nothing was sent anywhere to move time: the test holds the clock, and the application reads it.

When the application does not run in-process​

A loopback, container, AppHost or deployed application resolves its own time provider. The run cannot move that time, and a journey that needs the clock is an in-process journey. That is why the sample runs the API in-process for these tests and points the browser journey at a loopback instance. The test time reference names the attributes that skip a clock-dependent test when the run cannot serve it.

Checkpoint​

The clock advance in the trace reads 30:0:00:01. Why does the test move the clock to the period end minus the current time plus one second, instead of a fixed number of days?

Verify
Download l3-clock-window.prototrace, open it in the viewer, select the test, and find the clock.advance event on the test execution.

What you learned​

  • The test owns the clock; the in-process application reads it through its time provider.
  • Moving the clock is instant, and the trace records the delta and both instants.
  • A wait on real time changes nothing the application can see.

Keep exploring​