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:
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 | TestAppRuntime → AppRuntime 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