DRN.Framework.Testing
0.7.0-preview060
Prefix Reserved
dotnet add package DRN.Framework.Testing --version 0.7.0-preview060
NuGet\Install-Package DRN.Framework.Testing -Version 0.7.0-preview060
<PackageReference Include="DRN.Framework.Testing" Version="0.7.0-preview060" />
<PackageVersion Include="DRN.Framework.Testing" Version="0.7.0-preview060" />
<PackageReference Include="DRN.Framework.Testing" />
paket add DRN.Framework.Testing --version 0.7.0-preview060
#r "nuget: DRN.Framework.Testing, 0.7.0-preview060"
#:package DRN.Framework.Testing@0.7.0-preview060
#addin nuget:?package=DRN.Framework.Testing&version=0.7.0-preview060&prerelease
#tool nuget:?package=DRN.Framework.Testing&version=0.7.0-preview060&prerelease
DRN.Framework.Testing
Practical, effective testing helpers with data attributes, test context, and container orchestration for unit and integration tests.
TL;DR
- Auto-Mocking -
[DataInline]providesDrnTestContextand auto-mocks interface parameters with NSubstitute - Container Context - One-line Postgres/RabbitMQ container setup with auto-migration
- Application Context - Full
WebApplicationFactoryintegration with container awareness - Convention-Based - Settings and data files auto-discovered from test folder hierarchy
- DTT Pattern - Duran's Testing Technique for clean, hassle-free testing
Table of Contents
- QuickStart: Beginner
- QuickStart: Advanced
- DrnTestContext
- ContainerContext
- ApplicationContext
- Local Development Experience
- Data Attributes
- Unit Testing
- DebugOnly Tests
- DI Health Validation
- JSON Utilities
- Providers
- Global Usings
- Example Test Project
- Test Snippet
- Testing Guide and DTT Approach
QuickStart: Beginner
Write your first auto-mocked test in seconds:
[Theory]
[DataInline]
public void DataInlineDemonstration(DrnTestContext context, IMockable autoInlinedDependency)
{
context.ServiceCollection.AddApplicationServices();
//Context wraps service provider and automagically replaces actual dependencies with auto inlined dependencies
var dependentService = context.GetRequiredService<DependentService>();
autoInlinedDependency.Max.Returns(int.MaxValue); //dependency is already mocked by NSubstitute
dependentService.Max.Should().Be(int.MaxValue); //That is all. It is clean and effective
}
Testing models used in the QuickStart
public static class ApplicationModule //Can be defined in Application Layer or in Hosted App
{
public static void AddApplicationServices(this IServiceCollection serviceCollection)
{
serviceCollection.AddTransient<IMockable, ToBeRemovedService>(); //will be removed by test context because test method requested mocked interface
serviceCollection.AddTransient<DependentService>(); //dependent service uses IMockable and Max property returns dependency's Max value
}
}
public interface IMockable
{
public int Max { get; }
}
public class ToBeRemovedService : IMockable
{
public int Max { get; set; }
}
public class DependentService : IMockable
{
private readonly IMockable _mockable;
public DependentService(IMockable mockable)
{
_mockable = mockable;
}
public int Max => _mockable.Max;
}
QuickStart: Advanced
Advanced example with inlined values, auto-generated data, and mocked interfaces:
DataInlineprovidesDrnTestContextas first parameter- Then it provides inlined values
- Then it auto-generates missing values with AutoFixture
AutoFixturemocks any interface parameter withNSubstitute
/// <param name="context"> Provided by DataInline even if it is not a compile time constant</param>
/// <param name="inlineData">Provided by DataInline</param>
/// <param name="autoInlinedData">DataInline will provide missing data with the help of AutoFixture</param>
/// <param name="autoInlinedMockable">DataInline will provide implementation mocked by NSubstitute</param>
[Theory]
[DataInline(99)]
public void TextContext_Should_Be_Created_From_DrnTestContextData(DrnTestContext context, int inlineData, Guid autoInlinedData, IMockable autoInlinedMockable)
{
inlineData.Should().Be(99);
autoInlinedData.Should().NotBeEmpty(); //guid generated by AutoFixture
autoInlinedMockable.Max.Returns(int.MaxValue); //dependency mocked by NSubstitute
context.ServiceCollection.AddApplicationServices(); //you can add services, modules defined in hosted app, application, infrastructure layer etc..
var serviceProvider = context.BuildServiceProvider(); //appsettings.json added by convention. Context and service provider will be disposed by xunit
serviceProvider.GetService<ToBeRemovedService>().Should().BeNull(); //Service provider behaviour demonstration
var dependentService = serviceProvider.GetRequiredService<DependentService>();
dependentService.Max.Should().Be(int.MaxValue);
}
DrnTestContext
DrnTestContext has following properties:
- captures values provided to running test method, test method info and location.
- provides
ServiceCollectionso that to be tested services and dependencies can be added before buildingServiceProvider. - provides and implements lightweight
ServiceProviderthat contains default logging without any providerServiceProvidercan provide services that depends on likeILogger<DefaultService>- logged data will not be leaked to anywhere since it has no logging provider.
- provides
ContainerContext- can start
postgresandrabbitmqcontainers, apply migrations for dbContexts derived from DrnContext and updates connection string configuration with a single line of code
- can start
- provides
ApplicationContext- syncs
DrnTestContextservice collection and service provider with provided application by WebApplicationFactory - supports
ITestOutputHelperintegration for capturing application logs in test output
- syncs
- provides
FlurlHttpTestfor mocking external HTTP requests - provides
IConfigurationandIAppSettingswith SettingsProvider by using convention.- settings.json file can be found in the same folder with test
- settings.json file can be found in the global Settings folder or Settings folder that stays in the test folder
- Make sure file is copied to output directory
- If no settings file is specified while calling
BuildServiceProvider.appsettings.jsonfile be searched by convention.
- provides data file contents by using convention.
- data file can be found in the same folder with test
- data file can be found in the global Data folder or Data folder that stays in the test folder
- Make sure file is copied to output directory
- triggers
StartupJobRunnerto execute one-time test setup jobs marked withITestStartupJob ServiceProviderprovides utils provided with DRN.Framework.Utils'UtilsModuleBuildServiceProviderreplaces dependencies that can be replaced with inlined interfaces.ServiceProviderandDrnTestContextwill be disposed by xunit when test finishes- DI Health Check:
ValidateServicesAsync()ensures that all services added toServiceCollection(including those via attributes) can be resolved without runtime errors.
settings.json can be put in the same folder that test file belongs. This way providing and isolating test settings is much easier
[Theory]
[DataInline( "localhost")]
public void DrnTestContext_Should_Add_Settings_Json_To_Configuration(DrnTestContext context, string value)
{
//settings.json file can be found in the same folder with test file, in the global Settings folder or Settings folder that stays in the same folder with test file
context.GetRequiredService<IAppSettings>().GetRequiredSection("AllowedHosts").Value.Should().Be(value);
}
data.txt can be put in the same folder that test file belongs. This way providing and isolating test data is much easier
[Theory]
[DataInline("data.txt", "Atatürk")]
[DataInline("alternateData.txt", "Father of Turks")]
public void DrnTestContext_Should_Return_Test_Specific_Data(DrnTestContext context, string dataPath, string data)
{
//data file can be found in the same folder with test file, in the global Data folder or Data folder that stays in the same folder with test file
context.GetData(dataPath).Should().Be(data);
}
ContainerContext
With ContainerContext and conventions you can easily write effective integration tests against your database and message queue dependencies.
PostgreSQL Container
[Theory]
[DataInline]
public async Task QAContext_Should_Add_Category(DrnTestContext context)
{
context.ServiceCollection.AddSampleInfraServices();
await context.ContainerContext.Postgres.ApplyMigrationsAsync();
var qaContext = context.GetRequiredService<QAContext>();
var category = new Category("dotnet8");
qaContext.Categories.Add(category);
await qaContext.SaveChangesAsync();
category.Id.Should().BePositive();
}
- Application modules can be registered without any modification to
DrnTestContext DrnTestContext'sContainerContext- creates
postgresqlandrabbitmqcontainers then scans DrnTestContext's service collection for inherited DrnContexts. - Adds connection strings to DrnTestContext's configuration for each derived
DrnContextaccording to convention.
- creates
DrnTestContextacts as a ServiceProvider and when a service is requested it can build it from service collection with all dependencies.
RabbitMQ Container
You can start a RabbitMQ container for testing message queue integrations:
[Theory]
[DataInline]
public async Task RabbitMQ_Integration_Test(DrnTestContext context)
{
var container = await context.ContainerContext.RabbitMq.StartAsync();
var connectionString = container.GetConnectionString();
// Use connectionString for your message queue tests
}
Advanced Container Configuration
You can customize the Postgres container before starting it using PostgresContainerSettings:
[Theory]
[DataInline]
public async Task Custom_Container_Verification(DrnTestContext context)
{
// Configure settings before accessing ContainerContext.Postgres
PostgresContext.PostgresContainerSettings = new PostgresContainerSettings
{
ContainerName = "my-custom-db",
Database = "custom_db",
HostPort = 5440 // Bind to specific host port
};
await context.ContainerContext.StartPostgresAndApplyMigrationsAsync();
// ...
}
Isolated Containers
By default, DrnTestContext shares a single Postgres container across tests for performance. For scenarios requiring complete isolation (e.g., changing global system state), use PostgresContextIsolated:
[Theory]
[DataInline]
public async Task Isolated_Test_Run(DrnTestContext context)
{
// Starts a FRESH, exclusive container for this test
var container = await context.ContainerContext.Postgres.Isolated.ApplyMigrationsAsync();
// ... use the isolated container ...
}
Rapid Prototyping (No Migrations)
For rapid development where migrations are not yet created, use EnsureDatabaseAsync to create the schema directly from the model:
await context.ContainerContext.Postgres.Isolated.EnsureDatabaseAsync<MyDbContext>();
ApplicationContext
ApplicationContext syncs DrnTestContext service collection and service provider with provided application by WebApplicationFactory.
- You can provide or override configurations and services to your program until you force
WebApplicationFactoryto build aHostsuch as creatingHttpClientor requestingTestServer. - Supports
ITestOutputHelperintegration to capture application logs in test output
Basic Usage
[Theory]
[DataInline]
public async Task ApplicationContext_Should_Provide_Configuration_To_Program(DrnTestContext context)
{
var webApplication = context.ApplicationContext.CreateApplication<Program>();
await context.ContainerContext.Postgres.ApplyMigrationsAsync();
var client = webApplication.CreateClient();
var forecasts = await client.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
forecasts.Should().NotBeNull();
var appSettingsFromWebApplication = webApplication.Services.GetRequiredService<IAppSettings>();
var connectionString = appSettingsFromWebApplication.GetRequiredConnectionString(nameof(QAContext));
connectionString.Should().NotBeNull();
var appSettingsFromDrnTestContext = context.GetRequiredService<IAppSettings>();
appSettingsFromWebApplication.Should().BeSameAs(appSettingsFromDrnTestContext);//resolved from same service provider
}
Simplified Client Creation
For most API testing scenarios, use CreateClientAsync which handles common setup:
[Theory]
[DataInline]
public async Task Simplified_API_Test(DrnTestContext context, ITestOutputHelper output)
{
// Automatically starts containers, applies migrations, and returns authenticated client
var client = await context.ApplicationContext.CreateClientAsync<Program>(output);
var response = await client.GetAsync("/api/endpoint");
response.Should().BeSuccessful();
}
Test Output Logging
Capture application logs in test output for debugging:
[Theory]
[DataInline]
public async Task Test_With_Logging(DrnTestContext context, ITestOutputHelper output)
{
context.ApplicationContext.LogToTestOutput(output);
var app = context.ApplicationContext.CreateApplication<Program>();
// Application logs will appear in test output
}
Local Development Experience
DRN.Framework.Testing can be used to enhance the local development experience by providing infrastructure management capabilities to the main application during development.
Setup
To use this feature in your main application (not in test projects), you must add a reference to DRN.Framework.Testing that is only active in Debug configuration. This prevents test dependencies from leaking into production builds.
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<ProjectReference Include="..\DRN.Framework.Testing\DRN.Framework.Testing.csproj" />
</ItemGroup>
LaunchExternalDependenciesAsync
This extension method on WebApplicationBuilder automatically launches external dependencies (like Postgres, RabbitMQ) using Testcontainers when the application starts in a development environment.
// In your DrnProgramActions implementation (e.g., SampleProgramActions.cs)
#if DEBUG
public override async Task ApplicationBuilderCreatedAsync<TProgram>(
TProgram program, WebApplicationBuilder builder,
IAppSettings appSettings, IScopedLog scopedLog)
{
var launchOptions = new ExternalDependencyLaunchOptions
{
PostgresContainerSettings = new PostgresContainerSettings
{
Reuse = true, // Keep container running across restarts
HostPort = 6432 // Bind to a specific port to avoid conflicts
}
};
// Automatically starts containers if they are not already running
await builder.LaunchExternalDependenciesAsync(scopedLog, appSettings, launchOptions);
}
#endif
Launch Conditions
LaunchExternalDependenciesAsync is designed to be safe and non-intrusive. It only executes when all following conditions are met:
- Environment: Must be
Development. - Launch Flag:
AppSettings.DevelopmentSettings.LaunchExternalDependenciesmust betrue. - Not in Test:
TestEnvironment.DrnTestContextEnabledmust befalse(prevents collision with test containers). - Not Temporary:
AppSettings.DevelopmentSettings.TemporaryApplicationmust befalse.
This feature is particularly useful for:
- Onboarding: New developers can run the app without manually setting up infrastructure.
- Consistency: Ensures all developers use the same infrastructure configuration.
- Rapid Prototyping: Quickly spin up throwaway databases.
Data Attributes
DRN.Framework.Testing provides following data attributes that can provide data to tests:
- DataInlineAttribute
- DataMemberAttribute
- DataSelfAttribute
Following design principle is used for these attributes
- All attributes have data prefix to benefit from autocomplete
- All data attributes automatically provide
DrnTestContextas first parameter if tests requires - All data attributes try to provide missing values with AutoFixture and NSubstitute
- All data attributes will automatically override DrnTestContext's service collection with provided NSubstitute interfaces
- DataInline attribute works like xunit
InlineDataexcept they try to provide missing values with AutoFixture and NSubstitute - DataMember attribute works like xunit
MemberDataexcept they try to provide missing values with AutoFixture and NSubstitute - DateSelf attribute needs to be inherited by another class and should call
AddRowmethod in constructor to provide data
Example usages for DataMember attribute
[Theory]
[DataMember(nameof(DrnTestContextInlineMemberData))]
public void DrnTestContextMember_Should_Inline_And_Auto_Generate_Missing_Test_Data(DrnTestContext testContext,
int inline, ComplexInline complexInline, Guid autoGenerate, IMockable mock)
{
testContext.Should().NotBeNull();
testContext.TestMethod.Name.Should().Be(nameof(DrnTestContextMember_Should_Inline_And_Auto_Generate_Missing_Test_Data));
inline.Should().BeGreaterThan(10);
complexInline.Count.Should().BeLessThan(10);
autoGenerate.Should().NotBeEmpty();
mock.Max.Returns(75);
mock.Max.Should().Be(75);
}
public static IEnumerable<object[]> DrnTestContextInlineMemberData => new List<object[]>
{
new object[] { 11, new ComplexInline(8) },
new object[] { int.MaxValue, new ComplexInline(-1) }
};
Example usage for DataSelf attribute
public class DataSelfContextAttributeTests
{
[Theory]
[DataSelfContextTestData]
public void DrnTestContextClassData_Should_Inline_And_Auto_Generate_Missing_Test_Data(DrnTestContext testContext,
int inline, ComplexInline complexInline, Guid autoGenerate, IMockable mock)
{
testContext.Should().NotBeNull();
testContext.TestMethod.Name.Should().Be(nameof(DrnTestContextClassData_Should_Inline_And_Auto_Generate_Missing_Test_Data));
inline.Should().BeGreaterThan(98);
complexInline.Count.Should().BeLessThan(1001);
autoGenerate.Should().NotBeEmpty();
mock.Max.Returns(44);
mock.Max.Should().Be(44);
}
}
public class DataSelfContextTestData : DataSelfContextAttribute
{
public DataSelfContextTestData()
{
AddRow(99, new ComplexInline(100));
AddRow(199, new ComplexInline(1000));
}
}
Example usage for DataInline attribute
[Theory]
[DataInline(99)]
public void TextContext_Should_Be_Created_From_DrnTestContextData(DrnTestContext context, int inlineData, Guid autoInlinedData, IMockable autoInlinedMockable)
{
inlineData.Should().Be(99);
autoInlinedData.Should().NotBeEmpty(); //guid generated by AutoFixture
autoInlinedMockable.Max.Returns(int.MaxValue); //dependency mocked by NSubstitute
context.ServiceCollection.AddApplicationServices(); //you can add services, modules defined in hosted app, application, infrastructure layer etc..
var serviceProvider = context.BuildServiceProvider(); //appsettings.json added by convention. Context and service provider will be disposed by xunit
serviceProvider.GetService<ToBeRemovedService>().Should().BeNull(); //Service provider behaviour demonstration
var dependentService = serviceProvider.GetRequiredService<DependentService>();
dependentService.Max.Should().Be(int.MaxValue);
}
Unit Testing
For pure unit tests where you don't need the full dependency injection container or container orchestration, use DrnTestContextUnit and the corresponding Unit attributes.
Unit Attributes
[DataInlineUnit]: Same asDataInlinebut providesDrnTestContextUnit.[DataMemberUnit]: Same asDataMemberbut providesDrnTestContextUnit.[DataSelfUnit]: Same asDataSelfbut providesDrnTestContextUnit.
DrnTestContextUnit
Unlike DrnTestContext, DrnTestContextUnit is lightweight and focused on Method Context (managing test data and method info) without the overhead of ServiceCollection or ContainerContext.
[Theory]
[DataInlineUnit(99)]
public void Unit_Test_Example(DrnTestContextUnit context, int value, IMockable mock)
{
// Fast, lightweight, no container overhead
context.MethodContext.MethodName.Should().Be(nameof(Unit_Test_Example));
mock.Max.Returns(value);
var service = new DependentService(mock); // Manually inject dependencies
service.Max.Should().Be(99);
}
DebugOnly Tests
Following attributes can be used to run test only when the debugger is attached. These attributes does respect the attached debugger, not debug or release configuration.
- FactDebuggerOnly
- TheoryDebuggerOnly
DI Health Validation
Use ValidateServicesAsync() to catch missing dependency registrations before they fail your application at runtime.
[Theory]
[DataInline]
public async Task Dependency_Injection_Should_Be_Healthy(DrnTestContext context)
{
context.ServiceCollection.AddApplicationServices();
// Verifies that all registered services can be successfully resolved
await context.ValidateServicesAsync();
}
JSON Utilities
The JsonObjectExtensions provide a simple way to verify API contracts and serialization stability.
ValidateObjectSerialization
Ensures that an object can be serialized to JSON and deserialized back to an equivalent object.
[Theory]
[DataInline]
public void Contract_Should_RoundTrip_Successfully(MyContractDto dto)
{
// AutoFixture fills dto, then we verify round-trip
dto.ValidateObjectSerialization();
}
Providers
SettingsProvider
SettingsProvider gets the settings from Settings folder. Settings file path is relative Settings folder. Settings folder must be created in the root of the test Project. Make sure the settings file is copied to output directory.
[Fact]
public void SettingsProvider_Should_Return_IAppSettings_Instance()
{
var appSettings = SettingsProvider.GetAppSettings();
appSettings.GetRequiredSection("AllowedHosts").Value.Should().Be("*");
appSettings.TryGetSection("Bar", out _).Should().BeTrue();
appSettings.TryGetSection("Foo", out _).Should().BeFalse();
appSettings.GetRequiredConnectionString("Foo").Should().Be("Bar");
appSettings.TryGetConnectionString("Bar", out _).Should().BeFalse();
}
[Fact]
public void SettingsProvider_Should_Return_IConfiguration_Instance()
{
var configuration = SettingsProvider.GetConfiguration("secondaryAppSettings");
configuration.GetRequiredSection("AllowedHosts").Value.Should().Be("*");
configuration.GetSection("Foo").Exists().Should().BeTrue();
configuration.GetSection("Bar").Exists().Should().BeFalse();
configuration.GetConnectionString("Bar").Should().Be("Foo");
}
DataProvider
DataProvider gets the content of specified data file in the Data folder. Data file path is relative Data folder including file extension. Data folder must be created in the root of the test Project. Make sure the data file is copied to output directory.
[Fact]
public void DataProvider_Should_Return_Data_From_Test_File()
{
DataProvider.Get("Test.txt").Should().Be("Foo");
}
CredentialsProvider
CredentialsProvider is a helper class for generating and caching test usernames and passwords.
[Fact]
public void CredentialsProvider_Should_Generate_Test_User()
{
var credentials = CredentialsProvider.GenerateCredentials();
credentials.Username.Should().StartWith("testuser_");
credentials.Password.Length.Should().BeGreaterThanOrEqualTo(12);
}
xUnit Runner Configuration
xunit.runner.json is optional but recommended for configuring the test runner. When using Microsoft Testing Platform (MTP), this file ensures the runner behaves as expected (e.g., parallelization settings). Ensure this file is set to CopyToOutputDirectory in your csproj.
{
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
"diagnosticMessages": true,
"parallelizeAssembly": true,
"parallelizeTestCollections": true
}
Global Usings
Following global usings can be used in a Usings.cs file in test projects to reduce line of code in test files
global using Xunit;
global using Xunit.v3;
global using AutoFixture;
global using AutoFixture.AutoNSubstitute;
global using AutoFixture.Xunit3;
global using AwesomeAssertions;
global using NSubstitute;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.DependencyInjection.Extensions;
global using Microsoft.Extensions.Configuration;
global using DRN.Framework.Testing;
global using DRN.Framework.Testing.Contexts;
global using DRN.Framework.Testing.DataAttributes;
global using DRN.Framework.Testing.Providers;
global using DRN.Framework.Testing.TestAttributes;
global using DRN.Framework.Utils.Extensions;
global using DRN.Framework.Utils.Settings;
global using DRN.Framework.SharedKernel;
global using DRN.Framework.Utils.DependencyInjection;
global using System.Reflection;
global using System.IO;
global using System.Linq;
global using System.Collections;
global using Xunit.Abstractions;
Example Test Project .csproj File
Don't forget to replace DRN.Framework.Testing project reference with its nuget package reference
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<OutputType>Exe</OutputType>
<UseMicrosoftTestingPlatformRunner>true</UseMicrosoftTestingPlatformRunner>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="xunit.v3.mtp-v2" Version="3.2.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DRN.Framework.Testing\DRN.Framework.Testing.csproj"/>
</ItemGroup>
<ItemGroup>
<None Update="Settings\defaultAppSettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Data\Test.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Settings\secondaryAppSettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="xunit.runner.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
Test Snippet
dtt snippet for creating tests with a test context.
[Theory]
[DataInline]
public async Task $name$(DrnTestContext context)
{
$END$
}
Testing Guide and DTT Approach
DTT(Duran's Testing Technique) is developed upon following two ideas to make testing natural part of the software development:
- Writing a unit or integration test, providing settings and data to it should be easy, effective and encouraging as much as possible
- A test should test actual usage as much as possible.
DTT with DrnTestContext makes these ideas possible by
- being aware of test data and location
- effortlessly providing test data and settings
- effortlessly providing service collection
- effortlessly providing service provider
- effortlessly validating service provider
- effortlessly wiring external dependencies with Container Context
- effortlessly wiring application with Application Context With the help of test context, integration tests can be written easily with following styles.
- A data context attribute can provide NSubstituted interfaces and test context automatically replaces actual implementations with mocked interfaces and provides test data.
- Test containers can be used as actual dependencies instead of mocking them.
- With FactDebuggerOnly and TheoryDebuggerOnly attributes, cautiously written tests can use real databases and dependencies to debug production usage.
Comparison
Without DTT: Manual setup requires significant boilerplate to achieve dependency injection, mocking, and data generation.
[Fact]
public void Manual_Setup_Boilerplate_Fatigue()
{
// 1. Setup DI container
var services = new ServiceCollection();
// 2. Manual Mocking
var mockDependency = Substitute.For<IMockable>();
mockDependency.Max.Returns(99);
services.AddSingleton(mockDependency);
services.AddTransient<DependentService>();
// 3. Manual Data Generation
var fixture = new Fixture();
var autoGeneratedId = fixture.Create<Guid>();
// 4. Build Provider
var serviceProvider = services.BuildServiceProvider();
var systemUnderTest = serviceProvider.GetRequiredService<DependentService>();
// 5. Test logic...
}
With DTT: The same setup is handled declaratively, allowing you to focus immediately on the test logic.
[Theory]
[DataInline(99)]
public void DTT_Pit_Of_Success(DrnTestContext context, int value, IMockable mockDependency, Guid autoGeneratedId)
{
// DI, Mocking, and Data Generation are already done.
mockDependency.Max.Returns(value);
var systemUnderTest = context.GetRequiredService<DependentService>();
// ...
}
Scenario 2: Integration Testing (Containers + WebApp + Migrations)
Without DTT: Setting up a realistic integration test with Testcontainers, EF Core Migrations, and WebApplicationFactory requires understanding the lifecycle of multiple complex components.
public class Complex_Integration_Test : IAsyncLifetime
{
private PostgreSqlContainer _container;
private WebApplicationFactory<Program> _factory;
public async Task InitializeAsync()
{
// 1. Spin up Container
_container = new PostgreSqlBuilder().WithImage("postgres:15").Build();
await _container.StartAsync();
// 2. Configure WebApp to use Container
_factory = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder =>
{
builder.ConfigureAppConfiguration((_, config) =>
{
config.AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("ConnectionStrings:Default", _container.GetConnectionString())
});
});
});
// 3. Apply Migrations Manually
using var scope = _factory.Services.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>();
await dbContext.Database.MigrateAsync();
}
[Fact]
public async Task Manual_Integration_Pain()
{
var client = _factory.CreateClient();
// Test logic...
}
public async Task DisposeAsync()
{
await _factor.DisposeAsync();
await _container.DisposeAsync();
}
}
With DTT: DrnTestContext handles the entire lifecycle orchestration for you.
[Theory]
[DataInline]
public async Task DTT_Full_Integration_Magic(DrnTestContext context, ITestOutputHelper output)
{
// One line to rule them all:
// 1. Starts Postgres & RabbitMQ containers
// 2. Wires up connection strings to overrides
// 3. Applies EF Core Migrations
// 4. Bootstraps WebApplicationFactory
var client = await context.ApplicationContext.CreateClientAsync<Program>(output);
// Ready to test immediately
}
Willpower Depletion
Willpower is a finite daily resource. When testing requires high activation energy—manually wiring dependencies, managing container lifecycles, and writing boilerplate—it consumes this resource rapidly. This leads to "testing fatigue," where developers subconsciously avoid writing tests or delay them until the last possible moment.
DTT is designed to minimize this cognitive cost. By making the "correct" way to test (isolated, realistic, dependency-injected) also the easiest way to test, it preserves your willpower for solving business problems.
This encapsulates the core philosophy of "The Pit of Success"—a concept where the system design guides you to the right thing naturally, rather than fighting you.
- The "Disciplined Chore" (The Old Way): Usually, writing high-quality software tests—especially integration tests—requires friction. You have to manually spin up containers, wipe databases, and wire up DI boilerplate. Because this is hard and tedious, doing it correctly requires Discipline. When you are tired or under a deadline, that discipline fails, and you stop writing good tests.
- The "Path of Least Resistance" (The Human Nature): Humans are wired to conserve energy and gravitate towards the easiest possible action. Traditionally, the "Path of Least Resistance" is not writing tests or writing bad, fragile tests just to get it over with.
- The DTT Transformation: DTT flips this equation. By wrapping all that complex "rigour"—containers, migrations, DI, auto-mocking—into a single attribute (
[DataInline]), it makes the Result (High Quality Test) accessible via the Action (Lowest Effort).
It makes the "Right Thing" easier than the "Wrong Thing." You no longer need discipline to write great tests; you just need to follow the easiest path available to you.
With DTT, software testing becomes natural part of the software development.
Semper Progressivus: Always Progressive
Commit Info
Author: Duran Serkan KILIÇ
Date: 2026-01-28 22:51:48 +0300
Hash: d19f8038581d6fc4404b2c737ddb59e25a940c06
| 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
- AutoFixture.AutoNSubstitute (>= 4.18.1)
- AutoFixture.Xunit3 (>= 4.19.0)
- AwesomeAssertions (>= 9.3.0)
- DRN.Framework.EntityFramework (>= 0.7.0-preview060)
- DRN.Framework.Hosting (>= 0.7.0-preview060)
- Microsoft.AspNetCore.Mvc.Testing (>= 10.0.2)
- NSubstitute (>= 5.3.0)
- NSubstitute.Analyzers.CSharp (>= 1.0.17)
- Testcontainers.PostgreSql (>= 4.10.0)
- Testcontainers.RabbitMq (>= 4.10.0)
- xunit.v3.extensibility.core (>= 3.2.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Not every version includes changes, features or bug fixes. This project can increment version to keep consistency with other DRN.Framework projects.
## Version 0.7.0
My family celebrates the enduring legacy of Mustafa Kemal Atatürk's enlightenment ideals.
> [!WARNING]
> Since v0.6.0 (released 10 November 2024), substantial changes have occurred. This release notes file has been reset to reflect the current state of the project as of 29 January 2026. Previous history has been archived to maintain a clean source of truth based on the current codebase.
### New Features
* **DrnTestContext & DTT**
* **Full Integration Context**: `DrnTestContext` provides `ServiceCollection`, `ServiceProvider`, `Configuration`, and `FlurlHttpTest`.
* **Auto-Registration**: Automatically adds `DrnUtils` and executes `[StartupJob]`s for one-time setups.
* **Method Context**: Captures metadata for folder-based settings resolution.
* **DI Validation**: `ValidateServicesAsync()` for verifying service collection health and identifying missing dependencies early.
* **Lightweight Unit Context**: `DrnTestContextUnit` for pure unit tests without container overhead.
* **Container Orchestration**
* **ContainerContext**: Integrated management of PostgreSQL and RabbitMQ Testcontainers.
* **Auto-Wiring**: Scans for `DrnContext`s, creates containers, applies migrations, and injects connection strings automatically.
* **Modes**: Supports shared containers (fast) or `.Isolated` containers (independent data).
* **Rapid Prototyping**: `EnsureDatabaseAsync` for schema generation without migrations.
* **Application Integration**
* **ApplicationContext**: Deep integration with `WebApplicationFactory`.
* **Helpers**: `CreateClientAsync` (starts app + migrations + auth client), `CreateApplicationAndBindDependenciesAsync`, `LogToTestOutput`.
* **Local Development Experience**
* **Infrastructure Management**: `LaunchExternalDependenciesAsync` for `WebApplicationBuilder` to automatically start containers (Postgres, RabbitMQ) during development, ensuring zero-configuration onboarding for new developers.
* **Data Attributes (Auto-Mocking)**
* **DataInline**: Replaces `[InlineData]`. Auto-mocks interfaces (NSubstitute), fills missing params (AutoFixture), provides `DrnTestContext`.
* **DataMember**: Replaces `[MemberData]`. Source data from properties with auto-mocking support.
* **DataSelf**: Self-contained test data classes inheriting `DataSelfAttribute` (using `AddRow`).
* **Debugger Attributes**: `[FactDebuggerOnly]` and `[TheoryDebuggerOnly]` for running tests only during debugging sessions.
* **Providers & Utilities**
* **SettingsProvider**: Loads `appsettings.json` and overrides from `Settings/` folder or test-local folder.
* **DataProvider**: Loads test data files (e.g., `.json`, `.txt`) from `Data/` folder or test-local folder.
* **CredentialsProvider**: Generates unique, consistent usernames/passwords for test authentication.
* **JSON Utilities**: `ValidateObjectSerialization<T>()` for one-line JSON round-trip contract verification.
---
**Semper Progressivus: Always Progressive**
## Commit Info
Author: Duran Serkan KILIÇ
Date: 2026-01-28 22:51:48 +0300
Hash: d19f8038581d6fc4404b2c737ddb59e25a940c06