Skip to content
This repository was archived by the owner on May 17, 2026. It is now read-only.

Commit 3747062

Browse files
committed
Add new test bases and update logging system
Expanded the testing capabilities by creating HostApplicationTestBase and WebHostApplicationTestBase. These bases should make it easier to write and encapsulate common setup and teardown operations on tests involving WebHost and Host. Additionally, revised the logging system to include a "SimpleTestLogger" that operates without dependencies on the existing PulseFlow system. Made adjustments to related classes accordingly.
1 parent c43c04e commit 3747062

18 files changed

Lines changed: 386 additions & 43 deletions

Frank.Testing.EntityFrameworkCore/DbContextBuilder.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using Microsoft.EntityFrameworkCore;
2-
using Microsoft.EntityFrameworkCore.Infrastructure;
32
using Microsoft.Extensions.DependencyInjection;
43
using Microsoft.Extensions.Logging;
54

@@ -17,6 +16,8 @@ public class DbContextBuilder<T> where T : DbContext
1716

1817
private ILoggerFactory? _loggerFactory;
1918
private string _sqliteConnectionString = "Data Source=:memory:";
19+
20+
private string _databaseName = "TestDatabase";
2021

2122
public DbContextBuilder<T> WithLoggerProvider(ILoggerProvider loggerProvider)
2223
{
@@ -36,12 +37,30 @@ public DbContextBuilder<T> WithService<TService>(Action<IServiceCollection> conf
3637
return this;
3738
}
3839

40+
public DbContextBuilder<T> WithLoggerFactory(ILoggerFactory loggerFactory)
41+
{
42+
_loggerFactory = loggerFactory;
43+
return this;
44+
}
45+
3946
public DbContextBuilder<T> WithOptions(Action<DbContextOptionsBuilder<T>> configureOptions)
4047
{
4148
_configuredOptions = configureOptions as Action<DbContextOptionsBuilder>;
4249
return this;
4350
}
4451

52+
public DbContextBuilder<T> WithDatabaseName(string databaseName)
53+
{
54+
_databaseName = databaseName;
55+
return this;
56+
}
57+
58+
public DbContextBuilder<T> WithRandomDatabaseName()
59+
{
60+
_databaseName = Guid.NewGuid().ToString();
61+
return this;
62+
}
63+
4564
public T Build()
4665
{
4766
_serviceCollection.AddDbContext<T>(OptionsAction);

Frank.Testing.Logging/LoggingBuilderExtensions.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ public static class LoggingBuilderExtensions
1919
/// <returns>The modified ILoggingBuilder with the test logging added.</returns>
2020
public static ILoggingBuilder AddPulseFlowTestLoggingProvider(this ILoggingBuilder builder, ITestOutputHelper outputHelper, LogLevel logLevel = LogLevel.Debug)
2121
{
22-
builder.ClearProviders();
2322
builder.AddPulseFlow();
2423
builder.Services.AddSingleton(outputHelper);
2524
builder.Services.Configure<LoggerFilterOptions>(options =>
@@ -29,4 +28,21 @@ public static ILoggingBuilder AddPulseFlowTestLoggingProvider(this ILoggingBuild
2928
builder.Services.AddPulseFlow(flowBuilder => flowBuilder.AddFlow<TestLoggingOutputFlow>());
3029
return builder;
3130
}
32-
}
31+
32+
public static ILoggingBuilder AddSimpleTestLoggingProvider(this ILoggingBuilder builder, ITestOutputHelper outputHelper, LogLevel logLevel = LogLevel.Debug)
33+
{
34+
builder.Services.AddSingleton(outputHelper);
35+
builder.Services.Configure<LoggerFilterOptions>(options =>
36+
{
37+
options.MinLevel = logLevel;
38+
});
39+
builder.AddProvider<SimpleTestLoggerProvider>();
40+
return builder;
41+
}
42+
43+
public static ILoggingBuilder AddProvider<T>(this ILoggingBuilder builder) where T : class, ILoggerProvider
44+
{
45+
builder.Services.AddSingleton<ILoggerProvider, T>();
46+
return builder;
47+
}
48+
}

Frank.Testing.Logging/ServiceCollectionExtensions.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,21 @@ public static IServiceCollection AddTestLogging(this IServiceCollection services
2323
services.AddLogging(builder => builder.AddPulseFlowTestLoggingProvider(outputHelper, logLevel));
2424
return services;
2525
}
26+
27+
/// <summary>
28+
/// Adds simple test logging to the IServiceCollection.
29+
/// </summary>
30+
/// <param name="services">The IServiceCollection to add the test logging to.</param>
31+
/// <param name="outputHelper">The ITestOutputHelper to redirect the logging output to.</param>
32+
/// <param name="logLevel">The log level to use for the test logging. Default is LogLevel.Debug.</param>
33+
/// <returns>The modified IServiceCollection with the test logging added.</returns>
34+
public static IServiceCollection AddSimpleTestLogging(this IServiceCollection services, ITestOutputHelper outputHelper, LogLevel logLevel = LogLevel.Debug)
35+
{
36+
services.Configure<LoggerFilterOptions>(options =>
37+
{
38+
options.MinLevel = logLevel;
39+
});
40+
services.AddLogging(builder => builder.AddSimpleTestLoggingProvider(outputHelper, logLevel));
41+
return services;
42+
}
2643
}

Frank.Testing.Logging/SimpleTestLogger.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ public class SimpleTestLogger<T>(ITestOutputHelper outputHelper, LogLevel logLev
1111

1212
public class SimpleTestLogger(ITestOutputHelper outputHelper, LogLevel level, string categoryName) : ILogger
1313
{
14-
private string _categoryName = categoryName;
15-
1614
public IDisposable? BeginScope<TState>(TState state) where TState : notnull
1715
=> new PulseFlowLoggerScope<TState>(state);
1816

@@ -23,6 +21,6 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Except
2321
if (logLevel < level)
2422
return;
2523

26-
outputHelper.WriteLine(new LogPulse(logLevel, eventId, exception, _categoryName, formatter.Invoke(state, exception), state as IReadOnlyList<KeyValuePair<string, object?>>).ToString());
24+
outputHelper.WriteLine(new LogPulse(logLevel, eventId, exception, categoryName, formatter.Invoke(state, exception), state as IReadOnlyList<KeyValuePair<string, object?>>).ToString());
2725
}
2826
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System.Collections.Concurrent;
2+
3+
using Microsoft.Extensions.Logging;
4+
using Microsoft.Extensions.Options;
5+
6+
using Xunit.Abstractions;
7+
8+
namespace Frank.Testing.Logging;
9+
10+
public class SimpleTestLoggerProvider(ITestOutputHelper outputHelper, IOptionsMonitor<LoggerFilterOptions> options) : ILoggerProvider
11+
{
12+
private readonly ConcurrentDictionary<string, SimpleTestLogger> _loggers = new();
13+
14+
/// <inheritdoc />
15+
public void Dispose()
16+
{
17+
_loggers.Clear();
18+
}
19+
20+
/// <inheritdoc />
21+
public ILogger CreateLogger(string categoryName) => _loggers.GetOrAdd(categoryName, new SimpleTestLogger(outputHelper, options.CurrentValue.MinLevel, categoryName));
22+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
11+
<PackageReference Include="xunit.extensibility.core" Version="2.6.6" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<ProjectReference Include="..\Frank.Testing.Logging\Frank.Testing.Logging.csproj" />
16+
</ItemGroup>
17+
18+
<ItemGroup>
19+
<FrameworkReference Include="Microsoft.AspNetCore.App" />
20+
</ItemGroup>
21+
</Project>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using Frank.Testing.Logging;
2+
3+
using Microsoft.Extensions.Hosting;
4+
using Microsoft.Extensions.Logging;
5+
6+
using Xunit;
7+
using Xunit.Abstractions;
8+
9+
namespace Frank.Testing.TestBases;
10+
11+
public abstract class HostApplicationTestBase : IAsyncLifetime
12+
{
13+
private readonly HostApplicationBuilder _hostApplicationBuilder;
14+
private IHost? _host;
15+
private readonly CancellationTokenSource _cancellationTokenSource = new();
16+
private bool _initialized = false;
17+
18+
protected HostApplicationTestBase(ITestOutputHelper outputHelper, LogLevel logLevel = LogLevel.Information)
19+
{
20+
_hostApplicationBuilder = Host.CreateEmptyApplicationBuilder(new HostApplicationBuilderSettings());
21+
_hostApplicationBuilder.Logging.AddSimpleTestLoggingProvider(outputHelper, logLevel);
22+
}
23+
24+
public IServiceProvider Services => (_initialized ? _host?.Services : throw new InvalidOperationException("The host has not been initialized yet.")) ?? throw new InvalidOperationException("!!!");
25+
26+
protected virtual async Task SetupAsync(HostApplicationBuilder builder) => await Task.CompletedTask;
27+
28+
public async Task InitializeAsync()
29+
{
30+
await SetupAsync(_hostApplicationBuilder);
31+
_host = _hostApplicationBuilder.Build();
32+
await _host.StartAsync(_cancellationTokenSource.Token);
33+
_initialized = true;
34+
}
35+
36+
public async Task DisposeAsync()
37+
{
38+
await _cancellationTokenSource.CancelAsync();
39+
await _host?.StopAsync()!;
40+
await _host.WaitForShutdownAsync();
41+
_host.Dispose();
42+
}
43+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using Frank.Testing.Logging;
2+
3+
using Microsoft.AspNetCore;
4+
using Microsoft.AspNetCore.Hosting;
5+
using Microsoft.Extensions.Logging;
6+
7+
using Xunit;
8+
using Xunit.Abstractions;
9+
10+
namespace Frank.Testing.TestBases;
11+
12+
public abstract class WebHostApplicationTestBase : IAsyncLifetime
13+
{
14+
private readonly IWebHostBuilder _hostApplicationBuilder;
15+
private IWebHost? _host;
16+
private readonly CancellationTokenSource _cancellationTokenSource = new();
17+
private bool _initialized = false;
18+
19+
protected WebHostApplicationTestBase(ITestOutputHelper outputHelper, LogLevel logLevel = LogLevel.Information)
20+
{
21+
_hostApplicationBuilder = WebHost.CreateDefaultBuilder();
22+
_hostApplicationBuilder.ConfigureLogging(logging => logging.AddSimpleTestLoggingProvider(outputHelper, logLevel));
23+
}
24+
25+
public IServiceProvider Services => (_initialized ? _host?.Services : throw new InvalidOperationException("The host has not been initialized yet.")) ?? throw new InvalidOperationException("!!!");
26+
27+
protected virtual async Task SetupAsync(IWebHostBuilder builder) => await Task.CompletedTask;
28+
29+
protected HttpClient TestClient => (_initialized ? _host?.CreateTestClient() : throw new InvalidOperationException("The host has not been initialized yet.")) ?? throw new InvalidOperationException("!!!");
30+
protected IEnumerable<string> GetServerEndpoints() => (_initialized ? _host?.GetServerEndpoints() : throw new InvalidOperationException("The host has not been initialized yet.")) ?? throw new InvalidOperationException("!!!");
31+
public async Task InitializeAsync()
32+
{
33+
await SetupAsync(_hostApplicationBuilder);
34+
_host = _hostApplicationBuilder.Build();
35+
await _host.StartAsync(_cancellationTokenSource.Token);
36+
_initialized = true;
37+
}
38+
39+
public async Task DisposeAsync()
40+
{
41+
await _cancellationTokenSource.CancelAsync();
42+
await _host?.StopAsync()!;
43+
await _host.WaitForShutdownAsync();
44+
_host.Dispose();
45+
}
46+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using Microsoft.AspNetCore.Hosting;
2+
using Microsoft.AspNetCore.Hosting.Server;
3+
using Microsoft.AspNetCore.Hosting.Server.Features;
4+
using Microsoft.AspNetCore.Routing;
5+
using Microsoft.Extensions.DependencyInjection;
6+
7+
namespace Frank.Testing.TestBases;
8+
9+
public static class WebHostExtensions
10+
{
11+
public static HttpClient CreateTestClient(this IWebHost host)
12+
{
13+
var baseAddress = host.ServerFeatures.Get<IServerAddressesFeature>()?.Addresses.First();
14+
15+
return new HttpClient
16+
{
17+
BaseAddress = new Uri(baseAddress ?? throw new InvalidOperationException("The host has not been initialized yet."), UriKind.Absolute)
18+
};
19+
}
20+
21+
public static IEnumerable<string?> GetServerEndpoints(this IWebHost host)
22+
{
23+
return host.Services.GetServices<EndpointDataSource>().SelectMany(x => x.Endpoints).Select(x => x.DisplayName);
24+
}
25+
}

Frank.Testing.TestOutputExtensions/TestOutputCSharpExtensions.cs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,4 @@ public static void WriteCSharp<T>(this ITestOutputHelper outputHelper, IEnumerab
6363
SortDirection = ListSortDirection.Ascending,
6464
MaxDepth = 64,
6565
};
66-
}
67-
68-
public enum CSharpDumpType
69-
{
70-
Variable,
71-
Class,
72-
IEnumerable
7366
}

0 commit comments

Comments
 (0)