![]() |
VOOZH | about |
dotnet add package DRN.Framework.Testing --version 0.9.5
NuGet\Install-Package DRN.Framework.Testing -Version 0.9.5
<PackageReference Include="DRN.Framework.Testing" Version="0.9.5" />
<PackageVersion Include="DRN.Framework.Testing" Version="0.9.5" />Directory.Packages.props
<PackageReference Include="DRN.Framework.Testing" />Project file
paket add DRN.Framework.Testing --version 0.9.5
#r "nuget: DRN.Framework.Testing, 0.9.5"
#:package DRN.Framework.Testing@0.9.5
#addin nuget:?package=DRN.Framework.Testing&version=0.9.5Install as a Cake Addin
#tool nuget:?package=DRN.Framework.Testing&version=0.9.5Install as a Cake Tool
👁 master
👁 develop
👁 Quality Gate Status
👁 Security Rating
👁 Maintainability Rating
👁 Reliability Rating
👁 Vulnerabilities
👁 Bugs
👁 Lines of Code
👁 Coverage
Practical, effective testing helpers with data attributes, test context, and container orchestration for unit and integration tests.
[DataInline] / [DataInlineUnit] provide context objects and auto-mock interface parameters with NSubstituteWebApplicationFactory integration that syncs services/configuration and binds Postgres dependencies before client creationWrite 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
}
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;
}
Advanced example with inlined values, auto-generated data, and mocked interfaces:
DataInline provides DrnTestContext as first parameterAutoFixture mocks any interface parameter with NSubstitute/// <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 TestContext_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(); //settings.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 has following properties:
ServiceCollection so that to be tested services and dependencies can be added before building ServiceProvider.ServiceProvider that contains default logging without any provider
ServiceProvider can provide services that depends on like ILogger<DefaultService>ContainerContext
postgres containers, apply migrations for registered DrnContext types, and update connection string configuration with a single line of codeCreateClientAsyncApplicationContext
DrnTestContext service collection and service provider with provided application by WebApplicationFactoryITestOutputHelper integration for capturing application logs in test outputFlurlHttpTest for mocking external HTTP requests (see FlurlHttpTest Integration)IConfiguration and IAppSettings with SettingsProvider by using convention.
BuildServiceProvider, settings.json is searched by convention.StartupJobRunner to execute one-time test setup jobs marked with ITestStartupJobServiceProvider provides utils provided with DRN.Framework.Utils' UtilsModuleBuildServiceProvider replaces dependencies that can be replaced with inlined interfaces.ServiceProvider and DrnTestContext will be disposed by xunit when test finishesValidateServicesAsync() ensures that attribute-registered services 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);
}
With ContainerContext and conventions you can easily write effective integration tests against your database and message queue dependencies.
[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();
}
DrnTestContextDrnTestContext's ContainerContext
DrnContext according to convention.DrnTestContext acts as a ServiceProvider and when a service is requested it can build it from service collection with all dependencies.You can start a RabbitMQ container for testing message queue integrations:
[Theory]
[DataInline]
public async Task RabbitMQ_Integration_Test(DrnTestContext context)
{
var container = await RabbitMQContext.StartAsync();
var connectionString = container.GetConnectionString();
// Use connectionString for your message queue tests
}
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.Postgres.ApplyMigrationsAsync();
// ...
}
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 ...
}
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 syncs DrnTestContext service collection and configuration with a WebApplicationFactory.
CreateClient() or TestServer is requested.CreateClientAsync<TProgram>() calls ContainerContext.BindExternalDependenciesAsync(), which applies Postgres migrations for registered DrnContext types. It does not start RabbitMQ.LogToTestOutput(output, debuggerOnly: true) captures application logs only when the debugger is attached by default.TestEnvironment.DrnTestContextEnabled = trueAppSettings.DevelopmentSettings.TemporaryApplication = true [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
}
For most API testing scenarios, use CreateClientAsync which handles common setup:
[Theory]
[DataInline]
public async Task Simplified_API_Test(DrnTestContext context, ITestOutputHelper output)
{
// Builds the app, binds Postgres dependencies, applies migrations, and returns an HttpClient
var client = await context.ApplicationContext.CreateClientAsync<Program>(output);
var response = await client.GetAsync("/api/endpoint");
response.Should().BeSuccessful();
}
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
}
DRN.Framework.Testing enhances local development by providing infrastructure management capabilities directly to the host application.
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>
This extension method on WebApplicationBuilder launches Postgres Testcontainers when the application starts in a development environment and the launch feature is enabled.
// 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
LaunchExternalDependenciesAsync is designed to be safe and non-intrusive. It only executes when all following conditions are met:
Development.AppSettings.DevelopmentSettings.LaunchExternalDependencies must be true.TestEnvironment.DrnTestContextEnabled must be false (prevents collision with test containers).AppSettings.DevelopmentSettings.TemporaryApplication must be false.This feature is particularly useful for:
The framework uses different strategies for connection string resolution. See the comprehensive flow diagram in .
| Scenario | Connection Source | Settings Used |
|---|---|---|
| Production/Staging | ConnectionStrings:{ContextName} |
Explicit config only |
| Local Debug | LaunchExternalDependenciesAsync() |
PostgresContainerSettings.DefaultPassword |
| Docker/K8s Dev | DrnContextDevelopmentConnection |
DbContextConventions->DrnContext_Dev* & postgres-password |
| DrnTestContext | ContainerContext.Postgres |
PostgresContainerSettings defaults |
Testcontainers Mode (tests or LaunchExternalDependencies = true):
postgres-password and DrnContext_Dev* settings are NOT usedThese settings are used only by DrnContextDevelopmentConnection for containerized development. They are NOT used by ContainerContext or LaunchExternalDependencies.
| Setting | Default | Source |
|---|---|---|
DrnContext_DevHost |
drn |
DbContextConventions.DevHostKey |
DrnContext_DevPort |
5432 |
DbContextConventions.DevPortKey |
DrnContext_DevUsername |
drn |
DbContextConventions.DevUsernameKey |
DrnContext_DevDatabase |
drn |
DbContextConventions.DevDatabaseKey |
postgres-password |
(required) | DbContextConventions.DevPasswordKey |
Usually set by appsettings.Development.json, environment variables, or config maps.
| Setting | Default | Source | Purpose |
|---|---|---|---|
DrnDevelopmentSettings:AutoMigrateDevelopment |
true |
DrnDevelopmentSettings.AutoMigrateDevelopment | Auto-migrate in Development |
DrnDevelopmentSettings:AutoMigrateStaging |
false |
DrnDevelopmentSettings.AutoMigrateStaging | Auto-migrate in Staging; migrations only |
DrnDevelopmentSettings:Prototype |
false |
DrnDevelopmentSettings.Prototype | Development-only DB recreation on model changes |
DrnDevelopmentSettings:LaunchExternalDependencies |
false |
DrnDevelopmentSettings.LaunchExternalDependencies | Launch local PostgreSQL Testcontainers |
DrnDevelopmentSettings:TemporaryApplication |
false |
DrnDevelopmentSettings.TemporaryApplication | Auto-set by tests to prevent collision |
PostgreSQL defaults used by ContainerContext.Postgres and LaunchExternalDependencies:
| Property | Default |
|---|---|
DefaultImage |
"postgres" |
DefaultVersion |
"18.4-alpine3.23" |
DefaultPassword |
"drn" |
Database |
"drn" |
Username |
"drn" |
RabbitMQ defaults used when a test explicitly calls RabbitMQContext.StartAsync():
| Property | Default |
|---|---|
DefaultImage |
"rabbitmq" |
DefaultVersion |
"4.2.3-management-alpine" |
Username |
unset |
Password |
unset |
RabbitMQ is explicit: it is not started by CreateClientAsync, BindExternalDependenciesAsync, or PostgreSQL binding.
SettingsProvider uses settings.json by convention. It resolves settings from the test-local folder or a Settings/ folder and passes the selected base name to AddDrnSettings.
DrnTestContext and ApplicationContext set test-isolation flags before application hosts are built:
// Automatically set by ApplicationContext.CreateApplication<TProgram>()
TestEnvironment.DrnTestContextEnabled = true;
AppSettings.DevelopmentSettings.TemporaryApplication = true;
What This Means:
TemporaryApplication = true: Prevents test runs from interfering with local dev containersDrnTestContextEnabled = true: Signals test context, enabling test-specific behaviorsSee for the complete class definition.
DRN.Framework.Testing provides following data attributes that can provide data to tests:
Following design principle is used for these attributes
DrnTestContext as first parameter if tests requiresInlineData except they try to provide missing values with AutoFixture and NSubstituteMemberData except they try to provide missing values with AutoFixture and NSubstituteAddRow method in constructor to provide dataResolution order is identical for integration and unit variants: optional context first, then inline/member/self-provided values, then AutoFixture-generated values, then NSubstitute mocks for interface or abstract parameters. Request DrnTestContext or DrnTestContextUnit only when the test uses it. Interface substitutes are for dependencies; instantiate the concrete class when the concrete convenience method itself is the behavior under test.
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.MethodContext.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 DataSelfUnitAttributeTests
{
[Theory]
[DataSelfUnitTestData]
public void DrnTestContextClassData_Should_Inline_And_Auto_Generate_Missing_Test_Data(DrnTestContextUnit testContext,
int inline, ComplexInline complexInline, Guid autoGenerate, IMockable mock)
{
testContext.Should().NotBeNull();
testContext.MethodContext.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 DataSelfUnitTestData : DataSelfUnitAttribute
{
public DataSelfUnitTestData()
{
AddRow(99, new ComplexInline(100));
AddRow(199, new ComplexInline(1000));
}
}
Example usage for DataInline attribute
[Theory]
[DataInline(99)]
public void TestContext_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(); //settings.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);
}
For pure unit tests that do not need container orchestration or full application startup, use DrnTestContextUnit and the corresponding Unit attributes.
[DataInlineUnit]: Same as DataInline but provides DrnTestContextUnit.[DataMemberUnit]: Same as DataMember but provides DrnTestContextUnit.DataSelfUnitAttribute: Base class for custom self-contained data attributes that provide DrnTestContextUnit.Unlike DrnTestContext, DrnTestContextUnit is lightweight and focused on method data, method metadata, configuration, and optional unit-level service validation. It does not provide ContainerContext, ApplicationContext, FlurlHttpTest, or full app startup.
[Theory]
[DataInlineUnit(99)]
public void Unit_Test_Example(DrnTestContextUnit context, int value, IMockable mock)
{
// Fast, lightweight, no container overhead
context.MethodContext.TestMethod.Name.Should().Be(nameof(Unit_Test_Example));
mock.Max.Returns(value);
var service = new DependentService(mock); // Manually inject dependencies
service.Max.Should().Be(99);
}
If tests share the same setup and their consolidation creates no semantic or performance issue, they should be unified. Apply when consolidation requires only minimal essential change.
When multiple test cases share identical test bodies and differ only in input/expected-output, consolidate them into a single [Theory] with multiple data attribute rows instead of writing separate methods.
Anti-pattern — separate methods for each case:
[Theory]
[DataInlineUnit]
public void Add_Should_Return_Positive_Sum(DrnTestContextUnit context)
{
var calc = new Calculator();
calc.Add(2, 3).Should().Be(5);
}
[Theory]
[DataInlineUnit]
public void Add_Should_Handle_Negatives(DrnTestContextUnit context)
{
var calc = new Calculator();
calc.Add(-1, -2).Should().Be(-3);
}
[Theory]
[DataInlineUnit]
public void Add_Should_Handle_Zero(DrnTestContextUnit context)
{
var calc = new Calculator();
calc.Add(0, 0).Should().Be(0);
}
Preferred — one parameterized method covering all permutations:
[Theory]
[DataInlineUnit(2, 3, 5)] // positive + positive
[DataInlineUnit(-1, -2, -3)] // negative + negative
[DataInlineUnit(0, 0, 0)] // zeros
[DataInlineUnit(-1, 1, 0)] // cancellation
public void Add_Should_Return_Correct_Sum(DrnTestContextUnit context, int a, int b, int expected)
{
var calc = new Calculator();
calc.Add(a, b).Should().Be(expected);
}
When tests share identical setup (container init, migrations, service registration) and additional assertions can be applied by continuing the existing test flow, unify into a single test. Prevents code duplication, maintenance burden, and redundant setup/teardown cost. Most valuable in integration tests where setup is expensive.
Reference: QAContextTagTests.cs — single test flow validating entity IDs, JSON model queries, date filters, and materialization interceptor with one shared setup.
DrnTestContext or DrnTestContextUnit as a parameter only when the test body uses it; omit it for pure logic tests that need no contextFollowing 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.
Use ValidateServicesAsync() to catch missing dependencies for attribute-registered services before they fail your application at runtime.
[Theory]
[DataInline]
public async Task Dependency_Injection_Should_Be_Healthy(DrnTestContext context)
{
context.ServiceCollection.AddApplicationServices();
// Verifies that attribute-registered services can be successfully resolved
await context.ValidateServicesAsync();
}
The JsonObjectExtensions provide a simple way to verify API contracts and serialization stability.
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();
}
DrnTestContext provides built-in support for mocking HTTP requests via Flurl.Http.Testing. This enables testing services that make external API calls without hitting real endpoints.
[Theory]
[DataInline]
public async Task External_API_Should_Be_Mocked(DrnTestContext context)
{
// Setup mock response
context.FlurlHttpTest.RespondWith("{ \"status\": \"ok\" }", 200);
context.ServiceCollection.AddSingleton<IExternalApiClient, ExternalApiClient>();
var client = context.GetRequiredService<IExternalApiClient>();
var result = await client.GetStatusAsync();
result.Status.Should().Be("ok");
// Verify the request was made
context.FlurlHttpTest.ShouldHaveCalled("https://api.example.com/status")
.WithVerb(HttpMethod.Get)
.Times(1);
}
[Theory]
[DataInline]
public async Task Service_Should_Handle_API_Failure(DrnTestContext context)
{
// Simulate server error
context.FlurlHttpTest.RespondWith(status: 500);
context.ServiceCollection.AddSingleton<IExternalApiClient, ExternalApiClient>();
var client = context.GetRequiredService<IExternalApiClient>();
var act = async () => await client.GetStatusAsync();
await act.Should().ThrowAsync<FlurlHttpException>();
}
[Theory]
[DataInline]
public async Task Retry_Logic_Should_Work(DrnTestContext context)
{
// First call fails, second succeeds (testing retry logic)
context.FlurlHttpTest
.RespondWith(status: 503)
.RespondWith("{ \"status\": \"ok\" }", 200);
// ... test retry behavior
}
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 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 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.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
}
Run test projects directly with Microsoft Testing Platform. Do not use .slnx for test execution; run unit tests first, then integration tests only after unit tests pass.
dotnet run --project DRN.Test.Unit/DRN.Test.Unit.csproj
dotnet run --project DRN.Test.Integration/DRN.Test.Integration.csproj
For WebApplicationFactory<TProgram> scenarios, point TProgram at a hosted application or a non-test Web SDK support assembly such as DRN.Test.Utils (IsTestProject=false). Keep custom disposable app entry points out of the MTP test executable; keep test assertions in DRN.Test.Integration.
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\settings.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>
dtt snippet for creating tests with a test context.
[Theory]
[DataInline]
public async Task $name$(DrnTestContext context)
{
$END$
}
DTT (Duran's Testing Technique) is a context-oriented testing approach developed to make testing a natural part of software development. Instead of scattering setup across fixtures, factories, and lifecycle hooks, DTT places a single test context at the center of the test. The context adapts to the test's scope. It is lightweight for unit tests (DrnTestContextUnit), full-stack for integration tests (DrnTestContext).
DTT is built upon two core ideas:
DTT with DrnTestContext makes these ideas possible by
The context is opt-in: declare it as a parameter when the test needs it, omit it for pure logic tests that require no context. Data attributes inject the context only when the method signature requests it.
With the help of test context, integration tests can be written easily with following styles.
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. Binds Postgres dependencies
// 2. Wires up connection strings to overrides
// 3. Applies EF Core Migrations
// 4. Bootstraps WebApplicationFactory
// RabbitMQ is explicit: call RabbitMQContext.StartAsync() when a test needs it.
var client = await context.ApplicationContext.CreateClientAsync<Program>(output);
// Ready to test immediately
}
DTT reduces test setup friction so tests can focus on behavior instead of repeated infrastructure wiring.
[DataInline] and [DataInlineUnit] provide context, generated data, and mocked interfaces declaratively.DrnTestContext centralizes DI, settings/data lookup, external dependency binding, and application startup helpers.The result is a consistent path for writing isolated unit tests and realistic integration tests with minimal ceremony.
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.Contexts.Postgres;
global using DRN.Framework.Testing.Contexts.RabbitMQ;
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;
Add the following to your shell profile (e.g., ~/.zshrc or ~/.bashrc) to opt out of telemetry:
# Opt out of .NET CLI telemetry
export DOTNET_CLI_TELEMETRY_OPTOUT=1
# Opt out of .NET Testing Platform telemetry
export TESTINGPLATFORM_TELEMETRY_OPTOUT=1
References:
For complete examples, see Sample.Hosted.
Documented with the assistance of DiSC OS
Semper Progressivus: Always Progressive
Author: Duran Serkan KILIÇ
Date: 2026-06-14 21:30:19 +0300
Hash: ebe902574f06c0a2c2c0d8b4b2e28aafbfe418a6
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
This package is not used by any NuGet packages.
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.9.5 | 92 | 6/14/2026 |
| 0.9.5-preview011 | 89 | 6/14/2026 |
| 0.9.5-preview010 | 84 | 6/14/2026 |
| 0.9.5-preview009 | 85 | 6/14/2026 |
| 0.9.5-preview008 | 88 | 6/14/2026 |
| 0.9.5-preview007 | 79 | 6/13/2026 |
| 0.9.5-preview006 | 92 | 6/7/2026 |
| 0.9.5-preview005 | 92 | 6/7/2026 |
| 0.9.5-preview004 | 92 | 6/7/2026 |
| 0.9.5-preview003 | 91 | 6/6/2026 |
| 0.9.5-preview002 | 94 | 6/2/2026 |
| 0.9.5-preview001 | 91 | 6/1/2026 |
| 0.9.4 | 103 | 5/13/2026 |
| 0.9.3 | 128 | 4/25/2026 |
| 0.9.2 | 109 | 4/18/2026 |
| 0.9.1 | 118 | 3/26/2026 |
| 0.9.0 | 111 | 3/25/2026 |
| 0.9.0-preview001 | 113 | 3/22/2026 |
| 0.8.0 | 141 | 3/14/2026 |
| 0.7.0 | 112 | 3/8/2026 |
Not every version includes changes, features or bug fixes. This project can increment version to keep consistency with other DRN.Framework projects.
## Version 0.9.5
### Changed
* **PostgreSQL Testcontainers Default**: `PostgresContainerSettings.DefaultVersion` now uses `18.4-alpine3.23`.
## Version 0.9.4
Dependencies upgraded to dotnet 10.0.8
## Version 0.9.3
Dependencies upgraded to dotnet 10.0.7
## Version 0.9.2
Dependencies upgraded to dotnet 10.0.6
## Version 0.9.1
My family celebrates the enduring legacy of Mustafa Kemal Atatürk's enlightenment ideals and is proud to inherit his spiritual legacy: 'I am not leaving behind any definitive text, any dogma, any frozen, rigid rule as my spiritual legacy. My spiritual wealth is science and reason. Those who wish to embrace me after my death will become my spiritual heirs if they accept the guidance of reason and science on this fundamental axis.'
## Version 0.9.0
My family celebrates the enduring legacy of Mustafa Kemal Atatürk's enlightenment ideals and stands behind his remarkable words: 'Peace at home, peace in the world.'
## Version 0.8.0
My family celebrates the enduring legacy of Mustafa Kemal Atatürk's enlightenment ideals, rooted in his timeless words that 'science is the truest guide in life.' In that spirit, and to honor the 14 March Scientists Day, this release is dedicated to the researchers working for the benefit of humanity, and to the rejection of my first academic paper :) ([JOSS #10176](https://github.com/openjournals/joss-reviews/issues/10176)).
## Version 0.7.0
My family celebrates the enduring legacy of Mustafa Kemal Atatürk's enlightenment ideals and honors 8 March, International Women's Day, a cause inseparable from his vision of equality. This release is dedicated to freedom of speech, democracy, women's rights, and Prof. Dr. Ümit Özdağ, a defender 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 08 March 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 PostgreSQL Testcontainer binding for registered `DrnContext`s; RabbitMQ is available through an explicit opt-in helper.
* **Auto-Wiring**: Scans for `DrnContext`s, creates PostgreSQL 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 PostgreSQL containers when `IsDevelopmentEnvironment` is true; RabbitMQ tests call `RabbitMQContext.StartAsync()` explicitly.
* **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 `settings.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.
---
Documented with the assistance of [DiSC OS](https://github.com/duranserkan/DRN-Project/blob/develop/.agent/rules/DiSCOS.md)
---
**Semper Progressivus: Always Progressive**
## Commit Info
Author: Duran Serkan KILIÇ
Date: 2026-06-14 21:30:19 +0300
Hash: ebe902574f06c0a2c2c0d8b4b2e28aafbfe418a6