Skip to content

Testing Background Jobs

Deepstaging provides TestJobScheduler and TestJobStore — test doubles with call recording and typed query APIs. No mocking libraries needed.

TestJobScheduler

Records every Enqueue, Schedule, Cancel, and Retry call. Query recorded calls by payload type.

var scheduler = new TestJobScheduler();

// ... run your handler that enqueues jobs ...

// Assert a specific job type was enqueued
var emails = scheduler.Enqueued<SendWelcomeEmail>();
await Assert.That(emails).Count().IsEqualTo(1);
await Assert.That(emails[0].Email).IsEqualTo("alice@example.com");

Typed Queries

Method Returns
Enqueued<T>() IReadOnlyList<T> — all enqueued payloads of type T
Scheduled<T>() IReadOnlyList<(T Job, DateTimeOffset RunAt)> — scheduled payloads with run times

Raw Recording Lists

Property Type Description
EnqueueCalls IReadOnlyList<EnqueuedJob> All enqueue calls (untyped)
ScheduleCalls IReadOnlyList<ScheduledJob> All schedule calls (untyped)
CancelCalls IReadOnlyList<JobId> All cancel calls
RetryCalls IReadOnlyList<JobId> All retry calls

Configurable Responses

var scheduler = new TestJobScheduler();

// Configure GetStatusAsync to return a custom result
scheduler.OnGetStatus = id => new JobInfo(
    id, "SendWelcomeEmail", JobStatus.Completed, "{}",
    null, 1, 3, 1000, 300000, 0, null,
    DateTimeOffset.UtcNow, null, DateTimeOffset.UtcNow, null);

// Configure CancelAsync to return false
scheduler.OnCancel = _ => false;

TestJobStore

Records Complete, Fail, ClaimNext, and Heartbeat calls. Seed jobs for the worker to process.

var store = new TestJobStore();

// Seed a job for the worker to claim
store.Seed(new JobInfo(
    JobId.New(), "SendWelcomeEmail", JobStatus.Pending, payload,
    null, 0, 3, 1000, 300000, 0, null,
    DateTimeOffset.UtcNow, null, null, null));

// ... run the worker ...

// Assert job lifecycle
await Assert.That(store.CompletedJobIds).Count().IsEqualTo(1);

Assertion Properties

Property Type Description
CompletedJobIds IReadOnlyList<JobId> Jobs marked completed
FailedJobs IReadOnlyList<(JobId, string?)> Jobs marked failed, with error messages
ClaimedJobIds IReadOnlyList<JobId> Jobs claimed by the worker
HeartbeatCalls IReadOnlyList<JobId> Jobs that received heartbeats
Jobs IReadOnlyDictionary<JobId, JobInfo> All jobs in the store

Wiring Into TestRuntime

Wire the test doubles via OnConfigure():

[TestRuntime<AppRuntime>]
public partial class TestAppRuntime
{
    partial void OnConfigure() =>
        WithJobScheduler(new TestJobScheduler())
        .WithJobStore(new TestJobStore());
}

Then assert in your test:

[Test]
public async Task CreateOrder_EnqueuesWelcomeEmail()
{
    var runtime = TestAppRuntime.CreateConfigured();

    var program = AppDispatch.Dispatch(new CreateOrder("Alice"));
    await program.RunAsync(runtime);

    var scheduler = (TestJobScheduler)runtime.JobScheduler;
    await Assert.That(scheduler.Enqueued<SendWelcomeEmail>())
        .Single();
}

Reset Between Tests

Both test doubles support Reset() to clear recorded calls:

scheduler.Reset();
store.Reset();