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:
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using TestHost.Abstracts;
using TestHost.Abstracts.Logging;
public class TestApplication : TestHostApplication
{
protected override void ConfigureApplication(HostApplicationBuilder builder)
{
base.ConfigureApplication(builder);
// Add configuration sources
builder.Configuration.AddUserSecrets<TestApplication>();
// Configure logging with memory logger
builder.Logging.AddMemoryLogger();
// Register your services
builder.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.
using Microsoft.Extensions.DependencyInjection;
using TestHost.Abstracts.Logging;
public class MyServiceTests
{
[ClassDataSource<TestApplication>(Shared = SharedType.PerAssembly)]
public required TestApplication Application { get; init; }
[Test]
public async Task TestServiceBehavior()
{
// Arrange - Get service from DI container
var service = Application.Services.GetRequiredService<IMyService>();
// Act
service.DoSomething();
// Assert - Verify behavior
Assert.That(service.SomeProperty).IsTrue();
// Assert on log messages
var memoryLogger = Application.Services.GetService<MemoryLoggerProvider>();
var logs = memoryLogger?.Logs();
Assert.That(logs).Contains(log => log.Message.Contains("Expected log message"));
}
}The primary interface for test host applications:
public interface ITestHostApplication : IAsyncDisposable
{
/// <summary>
/// Gets the host for the tests.
/// </summary>
IHost Host { get; }
/// <summary>
/// Gets the services configured for this test host.
/// </summary>
IServiceProvider Services { 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
public class TestApplication : TestHostApplication
{
// Customize builder settings
protected override HostApplicationBuilderSettings? CreateBuilderSettings()
{
return new HostApplicationBuilderSettings
{
EnvironmentName = "Testing"
};
}
// Configure the application
protected override void ConfigureApplication(HostApplicationBuilder builder)
{
base.ConfigureApplication(builder);
// Your configuration here
}
// Custom host creation (advanced)
protected override IHost CreateHost()
{
// Custom host creation logic
return base.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
protected override void ConfigureApplication(HostApplicationBuilder builder)
{
base.ConfigureApplication(builder);
// Add memory logger with default settings
builder.Logging.AddMemoryLogger();
// Or with custom settings
builder.Logging.AddMemoryLogger(options =>
{
options.MinimumLevel = LogLevel.Debug;
options.Capacity = 2048;
options.Filter = (category, level) => category.StartsWith("MyApp");
});
}// Get the memory logger provider
var memoryLogger = Application.Services.GetService<MemoryLoggerProvider>();
// Get all logs
var allLogs = memoryLogger?.Logs();
// Get logs by category
var categoryLogs = memoryLogger?.Logs("MyApp.Services.MyService");
// Get logs by level (warning and above)
var warningLogs = memoryLogger?.Logs(LogLevel.Warning);
// Clear logs between tests
memoryLogger?.Clear();[Test]
public async Task VerifyLogging()
{
// Arrange
var service = Application.Services.GetRequiredService<IMyService>();
var logger = Application.Services.GetService<MemoryLoggerProvider>();
// Act
service.PerformAction();
// Assert
var logs = logger?.Logs();
await Assert.That(logs).IsNotEmpty();
await Assert.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.MsSqlusing Testcontainers.MsSql;
public class TestApplication : TestHostApplication, IAsyncInitializer
{
private readonly MsSqlContainer _msSqlContainer = new MsSqlBuilder()
.WithImage("mcr.microsoft.com/mssql/server:2022-latest")
.WithPassword("P@ssw0rd123!")
.Build();
public async Task InitializeAsync()
{
await _msSqlContainer.StartAsync();
}
protected override void ConfigureApplication(HostApplicationBuilder builder)
{
base.ConfigureApplication(builder);
var connectionString = _msSqlContainer.GetConnectionString();
builder.Services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(connectionString));
}
public override async ValueTask DisposeAsync()
{
await _msSqlContainer.DisposeAsync();
await base.DisposeAsync();
}
}public class DatabaseInitialize : IHostedService
{
private readonly IServiceProvider _serviceProvider;
public DatabaseInitialize(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
using var scope = _serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<MyDbContext>();
await context.Database.EnsureCreatedAsync(cancellationToken);
// Seed test data
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
// Register in ConfigureApplication
builder.Services.AddHostedService<DatabaseInitialize>();public class DatabaseTests
{
[ClassDataSource<TestApplication>(Shared = SharedType.PerAssembly)]
public required TestApplication Application { get; init; }
[Test]
public async Task GetUser_WithValidId_ReturnsUser()
{
// Arrange
var dbContext = Application.Services.GetRequiredService<SampleDataContext>();
// Act
var user = await dbContext.Users.FindAsync([1]);
// Assert
await Assert.That(user).IsNotNull();
await Assert.That(user.Name).IsEqualTo("Test User 1");
await Assert.That(user.Email).IsEqualTo("[email protected]");
}
[Test]
public async Task GetAllUsers_ReturnsSeededUsers()
{
// Arrange
var dbContext = Application.Services.GetRequiredService<SampleDataContext>();
// Act
var users = await dbContext.Users.ToListAsync();
// Assert
await Assert.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.