Skip to content

Testing

Deepstaging generates test infrastructure alongside your production code. Every module produces stubs, test doubles, or in-memory implementations — no mocking frameworks required.

Quick Start

Given a production runtime:

[EffectsModule(typeof(IEmailService))]
public sealed partial class EmailEffects;

[Runtime]
public sealed partial class AppRuntime;

Create a test runtime:

[TestRuntime<AppRuntime>]
public partial class TestAppRuntime;

Write a test:

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

    var result = await AppDispatch.SendWelcome("alice@example.com")
        .RunAsync(runtime);

    await Assert.That(result).IsSucc();
}

CreateConfigured() wires in-memory stubs for every capability. No DI container, no setup, no base class.

Dual-Mode Testing

The same test runs as a unit test or integration test:

dotnet test                      # unit — in-memory stubs
INTEGRATION=true dotnet test     # integration — real infrastructure via Testcontainers

CreateConfigured() calls OnConfigure() in unit mode and OnConfigureIntegration() in integration mode. Tests never check TestMode.IsIntegration directly.

[TestRuntime<AppRuntime>]
public partial class TestAppRuntime
{
    partial void OnConfigure() =>
        WithStubEmailService(stub => stub)
        .WithStubOrderStore(stub => stub);

    partial void OnConfigureIntegration()
    {
        OnConfigure();
        IntegrationDefaults.Configure(this);
    }
}

What Gets Generated

Declaration Generated
[TestRuntime<T>] Partial class with all capability interfaces
Per capability With{Name}() + WithStub{Name}() builders + inner Stub{Name} record
CreateConfigured() Factory calling OnConfigure() or OnConfigureIntegration() by mode
Implicit conversion TestAppRuntimeAppRuntime for RunAsync

Assertion Extensions

Deepstaging.Testing includes TUnit assertions for LanguageExt types:

await Assert.That(result).IsSucc();
await Assert.That(result).IsSuccEqualTo(expected);
await Assert.That(result).IsSuccMatching(v => v.Name == "test");

// Validation error assertions
await Assert.That(result).IsValidationError(v => v
    .HasField("Email", "required")
    .HasField("Name", "too_short")
    .HasCount(2));
Type Assertions
Fin<A> IsSucc(), IsFail(), IsSuccEqualTo(), IsSuccMatching(), IsValidationError()
Either<L, R> IsRight(), IsLeft(), IsRightEqualTo(), IsRightMatching()
Option<A> IsSome(), IsNone(), IsSomeEqualTo(), IsSomeMatching()

Testing Generators & Analyzers (RoslynTestBase)

If you're testing the Deepstaging source generators, analyzers, or code fixes themselves, RoslynTestBase provides four patterns:

Generator Tests

await GenerateWith<TypedIdGenerator>(source)
    .ShouldGenerate()
    .WithFileContaining("public partial struct UserId")
    .WithNoDiagnostics()
    .VerifySnapshot();

Analyzer Tests

await AnalyzeWith<TypedIdMustBePartialAnalyzer>(source)
    .ShouldReportDiagnostic("DSID01")
    .WithSeverity(DiagnosticSeverity.Error)
    .WithMessage("*UserId*partial*");

CodeFix Tests

await AnalyzeAndFixWith<TypedIdMustBePartialAnalyzer, StructMustBePartialCodeFix>(source)
    .ForDiagnostic("DSID01")
    .ShouldProduce(expectedSource);

Writer Unit Tests

var emit = SymbolsFor(source)
    .RequireNamedType("EmailEffects")
    .QueryEffectsModules().First()
    .WriteCapabilityInterface();
await Assert.That(emit).IsSuccessful();

All test projects inherit from RoslynTestBase (from Deepstaging.Roslyn.Testing) and register assembly references via [ModuleInitializer] in TestConfiguration.cs.

Next

  • Test Runtime — full reference for [TestRuntime<T>], stub configuration, and usage patterns
  • Dual-Mode Testing — Testcontainers setup, WebTestHost, CI configuration
  • Module Test Patterns — per-module testing: DataStore, HttpClient, EventQueue, Jobs