MartinDrozdik.DDD.Testing
0.7.1.1
dotnet add package MartinDrozdik.DDD.Testing --version 0.7.1.1
NuGet\Install-Package MartinDrozdik.DDD.Testing -Version 0.7.1.1
<PackageReference Include="MartinDrozdik.DDD.Testing" Version="0.7.1.1" />
<PackageVersion Include="MartinDrozdik.DDD.Testing" Version="0.7.1.1" />
<PackageReference Include="MartinDrozdik.DDD.Testing" />
paket add MartinDrozdik.DDD.Testing --version 0.7.1.1
#r "nuget: MartinDrozdik.DDD.Testing, 0.7.1.1"
#:package MartinDrozdik.DDD.Testing@0.7.1.1
#addin nuget:?package=MartinDrozdik.DDD.Testing&version=0.7.1.1
#tool nuget:?package=MartinDrozdik.DDD.Testing&version=0.7.1.1
DDD Testing Library
Common test tooling for the DDD libraries. Provides test fixtures, utilities, and helpers to make testing your DDD applications easier and more consistent. Check the demo tests for examples of how to use it in real tests.
Installation
dotnet add package xunit.v3
dotnet add package MartinDrozdik.DDD.Testing
Philosophy
- Write tests, not test infrastructure - Stop copy-pasting the same
WebApplicationFactoryboilerplate across every project. - Integration tests, not unit tests - Test the whole stack with real dependencies – reveals much more bugs. Unit tests still valuable tho.
- Composition over inheritance – Avoid the giant base test class pitfall.
- Consistent with the rest of the DDD stack - Same pragmatic approach, same "use what makes sense" attitude.
- xUnit - It has more stars than NUnit lmao.
You still have to write your own tests. But at least you don't have to write the boring parts.
Quick Start
Spin up your app in memory, make requests, assert things. Zero extra ceremony:
// 1. Subclass the builder for your Program.cs (once, in your test project)
public class MyAppBuilder(ITestOutputHelper output) : TestedAppBuilder<Program>(output)
{
MyAppBuilder(ITestOutputHelper output) : base(output)
{
// Add more configuration here
// Or leave it empty. Nobody's judging.
}
}
// 2. Use it in your tests
public class MyTests(ITestOutputHelper output) : IDisposable
{
private readonly TestedApp<Program> _app = new MyAppBuilder(output).Build();
// Or set specific extra config for this test class
private readonly TestedApp<Program> _app = new MyAppBuilder(output)
.WithOption<TestOptions>(e => e.SomeString, value)
.WithServices(services => services.AddAppOptions<TestOptions>())
.Build();
[Fact]
public async Task Something_works()
{
var client = _app.CreateClient();
var response = await client.GetAsync("/something");
response.EnsureSuccessStatusCode();
}
[Fact]
public async Task Something_other_works()
{
// Or use the builder directly here
var app = new MyAppBuilder(output).Build();
var client = app.CreateClient();
var response = await client.GetAsync("/something");
response.EnsureSuccessStatusCode();
}
public void Dispose() => _app.Dispose();
}
TestedApp & TestedAppBuilder
The TestedApp<TProgram> wraps WebApplicationFactory<TProgram> (yes, the standard one) and adds the things you always end up adding yourself anyway:
- xUnit log output – Your logs actually show up in the test runner. revoLuTiOnAry.
- You would be surprised how many people don't do this – and then wonder why they have no logs when their tests fail.
- Test endpoint injection – Register extra endpoints at test time without touching your production app.
- Config and options overrides – Change settings for tests without affecting production defaults.
- Disposable tracking – Attach anything that needs cleanup and it gets disposed with the factory.
- Mode minor goodies
Build it with the fluent TestedAppBuilder<TProgram>:
var app = new MyAppBuilder(output)
.WithConfig(builder => builder.UseSetting("SomeSetting", "SomeValue"))
.WithOption<DatabaseOptions>(o => o.ConnectionString, "Data Source=:memory:")
.WithServices(services => services.AddSingleton<IMyService, MockMyService>())
.WithEndpoints(endpoints => endpoints.MapGet("/test-only-route", () => "hello"))
.WithDisposable(() => Console.WriteLine("cleaned up, congrats"))
.WithEnvironment(AppEnvironments.Testing)
.WithUserAndRoles("testuser", ["Admin", "User"])
.WithClaimsPrincipal(...)
.Build();
Each With* call stacks – multiple configs, multiple endpoints, multiple disposables, all applied in order. Readable. Composable. Not a pyramid of constructors.
Call Dispose() when you're done.
Testing environment
By default, the tests run in the "Testing" environment. You can change that via the .WithEnvironment("Development") builder method.
This is to strictly separate your test configuration from your development configuration. You can have different appsettings files, different DI registrations, etc., for "Testing" vs "Development".
Use the AppEnvironments.Testing constant or the classic app.Environment.IsTesting() to check if you're running in the Testing environment.
Smoke Tests
Free tests that verify the basics. Because "it starts and doesn't explode" is a valid and surprisingly often-failing test.
Verify the app starts and is healthy:
public class MyAppSmokeTests(ITestOutputHelper output)
: WebApplicationSmokeTests<Program>(new MyAppBuilder(output))
{
}
Verifies that your OpenAPI document is valid JSON or YAML:
public class MyOpenApiSmokeTests(ITestOutputHelper output)
: OpenApiSmokeTests<Program>(new MyAppBuilder(output))
{
protected override IEnumerable<OpenApiEndpoint> GetOpenApiEndpoints() =>
[
new OpenApiEndpoint("/openapi/v1.json", OpenApiType.Json),
new OpenApiEndpoint("/openapi/v1.yaml", OpenApiType.Yaml),
];
}
Tests your error handling is wired up correctly:
public class MyAppErrorHandlingTests(ITestOutputHelper output)
: ErrorHandlingTests<Program>(new MyAppBuilder(output))
{
}
EF Core Context Tests
Tests that your EF Core mappings actually work against a real database. Because "the migration ran" does not mean "the query works":
public class MyDbContextTests(ITestOutputHelper testOutputHelper) : SqlDbContextIntegrationTests<MyDbContext>, IDisposable
{
private readonly TestedApp<Program> _factory =
new DemoAppBuilder(testOutputHelper).Build();
public void Dispose()
{
_factory.Dispose();
}
protected override MyDbContext GetContext()
{
return _factory.GetScopedService<MyDbContext>();
}
}
Assertions
EqualityAssert
Testing equality implementations is tedious and it's easy to miss edge cases. EqualityAssert covers IEquatable<T>, IEqualityComparer<T>, and equality operators (==, !=) in one shot:
[Fact]
public void InvoiceNumber_equality_is_correct()
{
var a = InvoiceNumber.Create(2024, 1);
var b = InvoiceNumber.Create(2024, 1); // same value, different instance
var c = InvoiceNumber.Create(2024, 2); // different value
EqualityAssert.TestAllEqualityBehaviors(a, b, c);
}
Or test just the parts you care about:
EqualityAssert.TestEquatable(a, b, c); // IEquatable<T>
EqualityAssert.TestEqualityComparer(a, b, c); // IEqualityComparer<T>
EqualityAssert.TestEqualityOperators(a, b, c); // == and != operators
Covers symmetry, null/default comparisons, and hash code consistency. Basically everything you'd forget to test manually.
ResultAssert
If you're using Result<T, E> or UnitResult<E> from CSharpFunctionalExtensions, these extension methods give you readable assertions:
[Fact]
public void CreateInvoice_returns_success()
{
var result = Invoice.Create(InvoiceNumber.Create(2024, 1));
result.IsSuccess();
}
Demo Tests
The demo test project shows all of this wired together with a real app. Check:
- DemoAppBuilder.cs – how to subclass
TestedAppBuilder - Smoke/* – smoke tests in practice
- Errors/TestProgramErrorHandlingTests.cs – error handling tests in practice
- Contexts/InvoiceDbContextTests.cs – EF Core integration tests in practice
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- MartinCostello.Logging.XUnit.v3 (>= 0.7.1)
- MartinDrozdik.DDD.Web (>= 0.7.0)
- Microsoft.AspNetCore.Mvc.Testing (>= 10.0.8)
- Microsoft.Extensions.Logging.Debug (>= 10.0.8)
- Microsoft.Extensions.TimeProvider.Testing (>= 10.6.0)
- xunit.v3.assert (>= 3.2.2)
- xunit.v3.extensibility.core (>= 3.2.2)
- YamlDotNet (>= 17.1.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.