A flexible and powerful test host builder for .NET applications that provides abstractions for hosting test applications with dependency injection, configuration, and in-memory logging support.
- Test Host Abstraction - Base classes and interfaces for creating test host applications
- Dependency Injection - Full integration with Microsoft.Extensions.DependencyInjection
- Configuration Support - Leverage Microsoft.Extensions.Configuration for test settings
- In-Memory Logger - Capture and assert on log messages during tests
- Async Lifecycle - Proper async initialization and disposal patterns
Install the package from NuGet:
dotnet add package TestHost.AbstractsOr via Package Manager Console:
Install-Package TestHost.AbstractsInherit from TestHostApplication to create your test host:
usingMicrosoft.Extensions.Configuration;usingMicrosoft.Extensions.DependencyInjection;usingMicrosoft.Extensions.Hosting;usingTestHost.Abstracts;usingTestHost.Abstracts.Logging;publicclassTestApplication:TestHostApplication{protectedoverridevoidConfigureApplication(HostApplicationBuilderbuilder){base.ConfigureApplication(builder);// Add configuration sourcesbuilder.Configuration.AddUserSecrets<TestApplication>();// Configure logging with memory loggerbuilder.Logging.AddMemoryLogger();// Register your servicesbuilder.Services.AddSingleton<IMyService,MyService>();}}Note: The examples below use TUnit, a modern .NET testing framework. You can also use this library with xUnit, NUnit, or MSTest.
usingMicrosoft.Extensions.DependencyInjection;usingTestHost.Abstracts.Logging;publicclassMyServiceTests{[ClassDataSource<TestApplication>(Shared=SharedType.PerAssembly)]publicrequiredTestApplicationApplication{get;init;}[Test]publicasyncTaskTestServiceBehavior(){// Arrange - Get service from DI containervarservice=Application.Services.GetRequiredService<IMyService>();// Actservice.DoSomething();// Assert - Verify behaviorAssert.That(service.SomeProperty).IsTrue();// Assert on log messagesvarmemoryLogger=Application.Services.GetService<MemoryLoggerProvider>();varlogs=memoryLogger?.Logs();Assert.That(logs).Contains(log =>log.Message.Contains("Expected log message"));}}The primary interface for test host applications:
publicinterfaceITestHostApplication:IAsyncDisposable{/// <summary>/// Gets the host for the tests./// </summary>IHostHost{get;}/// <summary>/// Gets the services configured for this test host./// </summary>IServiceProviderServices{get;}}Base class that implements ITestHostApplication with convenient lifecycle management:
- Thread-safe Host Creation - Lazy initialization with proper locking
- Configurable Builder - Override
CreateBuilderSettings()to customize host builder settings - Application Configuration - Override
ConfigureApplication()to configure services and logging - Async Disposal - Proper cleanup of host resources
publicclassTestApplication:TestHostApplication{// Customize builder settingsprotectedoverrideHostApplicationBuilderSettings?CreateBuilderSettings(){returnnewHostApplicationBuilderSettings{EnvironmentName="Testing"};}// Configure the applicationprotectedoverridevoidConfigureApplication(HostApplicationBuilderbuilder){base.ConfigureApplication(builder);// Your configuration here}// Custom host creation (advanced)protectedoverrideIHostCreateHost(){// Custom host creation logicreturnbase.CreateHost();}}The in-memory logger captures log messages during test execution for verification and debugging.
- Capture log entries in memory
- Query logs by category, log level, or custom filters
- Thread-safe log collection
- Configurable capacity and filtering
- Structured logging support with scopes and state
protectedoverridevoidConfigureApplication(HostApplicationBuilderbuilder){base.ConfigureApplication(builder);// Add memory logger with default settingsbuilder.Logging.AddMemoryLogger();// Or with custom settingsbuilder.Logging.AddMemoryLogger(options =>{options.MinimumLevel=LogLevel.Debug;options.Capacity=2048;options.Filter=(category,level)=>category.StartsWith("MyApp");});}// Get the memory logger providervarmemoryLogger=Application.Services.GetService<MemoryLoggerProvider>();// Get all logsvarallLogs=memoryLogger?.Logs();// Get logs by categoryvarcategoryLogs=memoryLogger?.Logs("MyApp.Services.MyService");// Get logs by level (warning and above)varwarningLogs=memoryLogger?.Logs(LogLevel.Warning);// Clear logs between testsmemoryLogger?.Clear();[Test]publicasyncTaskVerifyLogging(){// Arrangevarservice=Application.Services.GetRequiredService<IMyService>();varlogger=Application.Services.GetService<MemoryLoggerProvider>();// Actservice.PerformAction();// Assertvarlogs=logger?.Logs();awaitAssert.That(logs).IsNotEmpty();awaitAssert.That(logs).Contains(log =>log.LogLevel==LogLevel.Information&&log.Message.Contains("Action performed"));}Configure the memory logger with these options:
MinimumLevel- Minimum log level to capture (default:LogLevel.Debug)Capacity- Maximum number of log entries to keep (default: 1024)Filter- Custom filter function for fine-grained control
Log entries captured include:
Timestamp- DateTime when the log entry was createdLogLevel- The log level of the entry (Trace, Debug, Information, Warning, Error, Critical)EventId- Event identifier associated with the log entryCategory- Category name of the logger that created this entryMessage- Formatted log messageException- Exception associated with the log entry, if any (nullable)State- The state object passed to the logger (nullable)Scopes- Read-only collection of scope values that were active when the log entry was created
TestHost.Abstracts works seamlessly with Testcontainers to provide isolated database environments for integration tests. This approach uses IAsyncInitializer to manage container lifecycle and IHostedService to seed the database.
dotnet add package Testcontainers.MsSqlusingTestcontainers.MsSql;publicclassTestApplication:TestHostApplication,IAsyncInitializer{privatereadonlyMsSqlContainer_msSqlContainer=newMsSqlBuilder().WithImage("mcr.microsoft.com/mssql/server:2022-latest").WithPassword("P@ssw0rd123!").Build();publicasyncTaskInitializeAsync(){await_msSqlContainer.StartAsync();}protectedoverridevoidConfigureApplication(HostApplicationBuilderbuilder){base.ConfigureApplication(builder);varconnectionString=_msSqlContainer.GetConnectionString();builder.Services.AddDbContext<MyDbContext>(options =>options.UseSqlServer(connectionString));}publicoverrideasyncValueTaskDisposeAsync(){await_msSqlContainer.DisposeAsync();awaitbase.DisposeAsync();}}publicclassDatabaseInitialize:IHostedService{privatereadonlyIServiceProvider_serviceProvider;publicDatabaseInitialize(IServiceProviderserviceProvider){_serviceProvider=serviceProvider;}publicasyncTaskStartAsync(CancellationTokencancellationToken){usingvarscope=_serviceProvider.CreateScope();varcontext=scope.ServiceProvider.GetRequiredService<MyDbContext>();awaitcontext.Database.EnsureCreatedAsync(cancellationToken);// Seed test data}publicTaskStopAsync(CancellationTokencancellationToken){returnTask.CompletedTask;}}// Register in ConfigureApplicationbuilder.Services.AddHostedService<DatabaseInitialize>();publicclassDatabaseTests{[ClassDataSource<TestApplication>(Shared=SharedType.PerAssembly)]publicrequiredTestApplicationApplication{get;init;}[Test]publicasyncTaskGetUser_WithValidId_ReturnsUser(){// ArrangevardbContext=Application.Services.GetRequiredService<SampleDataContext>();// Actvaruser=awaitdbContext.Users.FindAsync([1]);// AssertawaitAssert.That(user).IsNotNull();awaitAssert.That(user.Name).IsEqualTo("Test User 1");awaitAssert.That(user.Email).IsEqualTo("user1@test.com");}[Test]publicasyncTaskGetAllUsers_ReturnsSeededUsers(){// ArrangevardbContext=Application.Services.GetRequiredService<SampleDataContext>();// Actvarusers=awaitdbContext.Users.ToListAsync();// AssertawaitAssert.That(users.Count).IsGreaterThanOrEqualTo(2);}}Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.