# Write Your First Test

A BackWave test enqueues work, advances a clock it controls, and asserts on the result. It never sleeps, polls, or races, so it gives the same answer on the first run and the ten-thousandth. This page gets you from an empty test project to one green test: install the package, build the harness, and follow the four-step loop with a complete example you can paste in.

You do not have to think about why this is reproducible to write the test. The short version is that the clock is a value your test moves explicitly, and the store underneath is real rather than a mock — the same [In-Memory Store](/docs/storage/overview) shipped for tests and local dev, implementing the same Storage Contract Postgres and SQL Server do, so the runtime behaves in a test exactly as it does in production. If you want the full account of how that determinism is built and exploited, it lives in [The Determinism Boundary](/docs/advanced/determinism-boundary) and [How BackWave Is Tested](/docs/testing/how-backwave-is-tested).

## Installing the Testing package

The harness ships in `BackWave.Testing`. Add it to your test project.

```bash title="Terminal"
dotnet add package BackWave.Testing
```

It brings the In-Memory Store with it, so a test project needs no Storage Adapter of its own.

## Building the harness

`BackWaveHarness` is the public doorway. You build it from your real Job Registry and your real DI services, the same two things your application wires up in production.

```csharp title="Harness construction"
var harness = new BackWaveHarness(BackWaveJobs.CreateRegistry(), services);
```

The registry is `BackWaveJobs.CreateRegistry()`, generated from the `[Job]` declarations in your solution exactly as shipped. The `IServiceProvider` resolves your handlers and their dependencies exactly as production would, which is where you swap real infrastructure for fakes: register the real handler and a fake gateway, and the handler under test never knows the difference.

A third `BackWaveHarnessOptions` argument is optional, and its defaults suit almost every test:

| Option | Default | Meaning |
|---|---|---|
| `StartTime` | Midnight UTC, 1 Jan 2026 | The fixed instant Virtual Time starts at, chosen for reproducibility rather than `DateTimeOffset.UtcNow`. |
| `Policy` | Serves every Queue the registry declares, plus `"default"` | The Dispatch Policy the single virtual Worker runs. |
| `RetryPolicy` | `RetryPolicy.Default` | The retry ceiling and backoff applied to failing jobs. A Job type that carries `[Retry]` uses its own instead, as it does in production. |
| `Retention` | Off | Retention is off so assertions can see the whole job history; opt in only when testing retention itself. |
| `Bounds` | In-memory store defaults | Payload and store size bounds. |

Once built, the harness exposes the surfaces a test reaches for:

| Member | What it is |
|---|---|
| `Now` | The current Virtual Time instant. Starts at `StartTime` and only moves when you advance. |
| `Monitor` | The read-only [Monitor API](/docs/dashboard-operations/monitor-api) you assert through, identical to the one production observability and the dashboard use. |
| `Client` | The `BackWaveClient` wired to the harness store and virtual clock, for enqueue overloads the harness does not surface directly. |
| `Store` | Direct access to the In-Memory Store for low-level assertions the Monitor does not expose. |

## The canonical loop

Every BackWave test is the same four steps.

1. **Define** the job and handler exactly as your application ships them. No test doubles for the job itself.
2. **Enqueue** it through the harness with `EnqueueAsync`, at `Now` or `Now + delay`.
3. **Advance** Virtual Time with `AdvanceAsync`, which runs everything that comes due in the interval.
4. **Assert** through `harness.Monitor`, the same read surface production uses.

`AdvanceAsync` is the engine. It does not jump straight to the target instant. It stops at and fully processes every intervening due instant in order, a released [Dependency](/docs/core-concepts/dependencies), a retry backoff, a Lease expiry, a recurring tick, before moving on, and applies time-based effects like retention purges at the final instant. It only moves forward: a negative `TimeSpan` throws `ArgumentOutOfRangeException`. When you need to drain work that is due right now without moving the clock, `RunDueAsync()` is an advance by zero.

## A complete example

This test defines a real job and handler, builds the harness with a fake gateway, enqueues due two days out, advances three days, and asserts the job Succeeded, all with no waiting.

```csharp title="SendInvoiceTests.cs" {17,22,27}
// 1. Define the job -- the same record and handler your app ships.
[Job("send-invoice")]
public sealed record SendInvoice(string OrderId);

public sealed class SendInvoiceHandler(IInvoiceGateway gateway) : IJobHandler<SendInvoice>
{
    public Task HandleAsync(SendInvoice job, JobContext context, CancellationToken cancellationToken)
        => gateway.SendAsync(job.OrderId, cancellationToken);
}

[Fact]
public async Task InvoiceGoesOut_TwoDaysAfterTheOrder()
{
    // 2. Build the harness from your registry and DI services.
    var services = new ServiceCollection()
        .AddSingleton<IInvoiceGateway, FakeInvoiceGateway>()
        .AddTransient<IJobHandler<SendInvoice>, SendInvoiceHandler>()
        .BuildServiceProvider();
    var harness = new BackWaveHarness(BackWaveJobs.CreateRegistry(), services);

    // 3. Enqueue due in two days, then advance three -- everything due in between runs.
    var jobId = await harness.EnqueueAsync(new SendInvoice("order-42"), delay: TimeSpan.FromDays(2));
    await harness.AdvanceAsync(TimeSpan.FromDays(3));

    // 4. Assert through the Monitor API -- the same surface production observability uses.
    var job = await harness.Monitor.GetJobAsync(jobId);
    Assert.Equal(JobState.Succeeded, job!.State);
}
```

`GetJobAsync` returns the job's current `JobSnapshot`, or `null` if no job with that id exists. `Succeeded` is one of the terminal `JobState` values, alongside `Cancelled`, `DeadLettered`, and `Quarantined`; the non-terminal states are `Scheduled`, `AwaitingParent`, and `Leased`. Asserting on `State` is the simplest assertion; the Monitor also exposes queue depths, the Transition Log, schedules, and dependency edges for richer checks. See [Assert on What Happened](/docs/testing/asserting-execution) for the full assertion vocabulary.

## Where to go next

The rest of this section builds on the loop above.

- [Test Behavior Over Time](/docs/testing/behavior-over-time): advancing the clock to drive delays, retries, backoff, recurring schedules, and dependency releases without waiting.
- [Assert on What Happened](/docs/testing/asserting-execution): the Monitor API in depth, from job state to the Transition Log to queue depths, plus verifying side effects with a fake.
- [Test Workflows](/docs/testing/workflows): driving Dependencies and, with BackWave Pro, Workflows through the harness.
- [Integration Testing](/docs/testing/integration-testing): graduating from the in-memory harness to a real database when you need to prove the adapter and real transactions.
- [Jobs & Handlers](/docs/core-concepts/jobs-and-handlers): the `[Job]` attribute, `IJobHandler<T>`, and the Wire Name your tests exercise.
