Skip to main content
net10.0net11.0net8.0

Deterministic harness, capturing storage, assertions, and storage-provider conformance tests for Immediate.Jobs.

Published
Oct 1, 2026
17d ago
Total Downloads
638
638

Install

dotnet add package Immediate.Jobs.Testing --version 0.6.2
ProductVersions
.NET
net10.0net11.0net8.0

README

Immediate.Jobs.Testing

NuGet Documentation License

Deterministic testing tools for Immediate.Jobs: a harness with fake time, advance-and-drain helpers, capturing in-memory storage, enqueue assertions, a single-job handler-pipeline runner, and storage-provider conformance tests.

Installation

dotnet add package Immediate.Jobs --prerelease
dotnet add package Immediate.Jobs.Testing --prerelease

Deterministic job tests

JobTestHarness hosts the production scheduling service with in-memory storage and a controllable clock, without starting background threads. Register the generated jobs, handlers, and their dependencies in its service collection:

await using var harness = new JobTestHarness(services =>
{
	services.AddMyAppHandlers();
	services.AddMyAppJobs();
	services.AddSingleton<IEmailSender, RecordingEmailSender>();
});

await using var scope = harness.Services.CreateAsyncScope();
var scheduler = scope.ServiceProvider.GetRequiredService<SendWelcomeEmail.Scheduler>();
var handle = await scheduler.ScheduleAsync(
	new(userId, "v2"),
	TimeSpan.FromMinutes(10),
	cancellationToken
);

var enqueued = await harness.AssertEnqueuedAsync<SendWelcomeEmail.Payload>(
	handle,
	JobState.Scheduled,
	cancellationToken
);
Assert.Equal(userId, enqueued.Payload.UserId);

await harness.AdvanceTimeAndDrainAsync(TimeSpan.FromMinutes(10), cancellationToken);
Assert.Equal(JobState.Succeeded, (await harness.GetJobAsync(handle, cancellationToken)).State);

Delayed work, scheduled occurrences, timeouts, and backoff tests do not need wall-clock sleeps. The harness also exposes persisted-job queries and focused assertions for batches, continuations, and dependency cascades. Register generated jobs in the callback, but do not call ConfigureStorage; the harness installs its own in-memory provider and fake clock.

Capturing scheduler calls

JobTestHarness installs CapturingJobStorage behind the production schedulers. Calls are recorded without preventing jobs from being queried, cancelled, or executed normally:

await using var harness = new JobTestHarness(services => services.AddMyAppJobs());
var scheduler = harness.Services.GetRequiredService<SendWelcomeEmail.Scheduler>();
var payload = new SendWelcomeEmail.Payload(userId, "v2");
var handle = await scheduler.EnqueueAsync(
	payload,
	groupId: "tenant-a",
	cancellationToken: cancellationToken
);

var captured = harness.Captures.FindJob(handle)!;
Assert.Equal(handle, captured.JobHandle);
Assert.Equal("tenant-a", captured.GroupId);

await scheduler.CancelAsync(handle, cancellationToken);
Assert.Equal(JobState.Cancelled, (await harness.GetJobAsync(handle, cancellationToken)).State);

The same CapturingJobStorage instance is available from harness.Captures and dependency injection. Its snapshots preserve call order for jobs, continuations, batches, dynamic batch additions, recurring definitions, and recurring materializations. Clear() resets only the capture log; it does not remove persisted jobs.

Use Jobs, Continuations, Batches, BatchJobs, DynamicContinuations, RecurringSchedules, RecurringOperations, and RecurringMaterializations when a test needs the complete call history rather than one job. Each property returns a snapshot, so assertions do not observe a collection changing underneath them.

Storage-provider conformance

If you are implementing a new IJobStorage provider, use JobStorageConformanceSuite to verify its shared behavior. Select the feature flags the provider supports, create a fresh service provider and backend for each case, and pass the service provider to RunAsync.

private const StorageCapabilities Capabilities =
	StorageCapabilities.Queue | StorageCapabilities.Recurring;

public static TheoryData<JobStorageConformanceTestCase> Cases =>
	[.. JobStorageConformanceSuite.GetCases(Capabilities)];

[Theory]
[MemberData(nameof(Cases))]
public async Task StorageConforms(JobStorageConformanceTestCase testCase)
{
	await using var services = await AcmeStorageFixture.CreateServiceProviderAsync();
	await testCase.RunAsync(services);
}

The fixture should use the provider's normal public registration method, register a FakeTimeProvider as TimeProvider, and isolate its database, schema, or key prefix. See the storage-provider testing documentation for isolation requirements and capability selection.

More information

Changes from Previous Version

No structural changes between v0.6.1 and v0.6.2.

Adoption Assessment

Assess this package against its supported frameworks, license, maintenance signals and dependency graph before making it a project standard.

Compatibility metadata lists 3 target frameworks; confirm the exact target used by your application.

Related Guides & Workflows

Learn how to evaluate, integrate, or audit this package in your .NET project.