Skip to content

Commit

Permalink
Add new test bases and update logging system
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
frankhaugen committed Jan 29, 2024
1 parent c43c04e commit 3747062
Show file tree
Hide file tree
Showing 18 changed files with 386 additions and 43 deletions.
21 changes: 20 additions & 1 deletion Frank.Testing.EntityFrameworkCore/DbContextBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

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

private ILoggerFactory? _loggerFactory;
private string _sqliteConnectionString = "Data Source=:memory:";

private string _databaseName = "TestDatabase";

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

public DbContextBuilder<T> WithLoggerFactory(ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
return this;
}

public DbContextBuilder<T> WithOptions(Action<DbContextOptionsBuilder<T>> configureOptions)
{
_configuredOptions = configureOptions as Action<DbContextOptionsBuilder>;
return this;
}

public DbContextBuilder<T> WithDatabaseName(string databaseName)
{
_databaseName = databaseName;
return this;
}

public DbContextBuilder<T> WithRandomDatabaseName()
{
_databaseName = Guid.NewGuid().ToString();
return this;
}

public T Build()
{
_serviceCollection.AddDbContext<T>(OptionsAction);
Expand Down
20 changes: 18 additions & 2 deletions Frank.Testing.Logging/LoggingBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ public static class LoggingBuilderExtensions
/// <returns>The modified ILoggingBuilder with the test logging added.</returns>
public static ILoggingBuilder AddPulseFlowTestLoggingProvider(this ILoggingBuilder builder, ITestOutputHelper outputHelper, LogLevel logLevel = LogLevel.Debug)
{
builder.ClearProviders();
builder.AddPulseFlow();
builder.Services.AddSingleton(outputHelper);
builder.Services.Configure<LoggerFilterOptions>(options =>
Expand All @@ -29,4 +28,21 @@ public static ILoggingBuilder AddPulseFlowTestLoggingProvider(this ILoggingBuild
builder.Services.AddPulseFlow(flowBuilder => flowBuilder.AddFlow<TestLoggingOutputFlow>());
return builder;
}
}

public static ILoggingBuilder AddSimpleTestLoggingProvider(this ILoggingBuilder builder, ITestOutputHelper outputHelper, LogLevel logLevel = LogLevel.Debug)
{
builder.Services.AddSingleton(outputHelper);
builder.Services.Configure<LoggerFilterOptions>(options =>
{
options.MinLevel = logLevel;
});
builder.AddProvider<SimpleTestLoggerProvider>();
return builder;
}

public static ILoggingBuilder AddProvider<T>(this ILoggingBuilder builder) where T : class, ILoggerProvider
{
builder.Services.AddSingleton<ILoggerProvider, T>();
return builder;
}
}
17 changes: 17 additions & 0 deletions Frank.Testing.Logging/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,21 @@ public static IServiceCollection AddTestLogging(this IServiceCollection services
services.AddLogging(builder => builder.AddPulseFlowTestLoggingProvider(outputHelper, logLevel));
return services;
}

/// <summary>
/// Adds simple test logging to the IServiceCollection.
/// </summary>
/// <param name="services">The IServiceCollection to add the test logging to.</param>
/// <param name="outputHelper">The ITestOutputHelper to redirect the logging output to.</param>
/// <param name="logLevel">The log level to use for the test logging. Default is LogLevel.Debug.</param>
/// <returns>The modified IServiceCollection with the test logging added.</returns>
public static IServiceCollection AddSimpleTestLogging(this IServiceCollection services, ITestOutputHelper outputHelper, LogLevel logLevel = LogLevel.Debug)
{
services.Configure<LoggerFilterOptions>(options =>
{
options.MinLevel = logLevel;
});
services.AddLogging(builder => builder.AddSimpleTestLoggingProvider(outputHelper, logLevel));
return services;
}
}
4 changes: 1 addition & 3 deletions Frank.Testing.Logging/SimpleTestLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ public class SimpleTestLogger<T>(ITestOutputHelper outputHelper, LogLevel logLev

public class SimpleTestLogger(ITestOutputHelper outputHelper, LogLevel level, string categoryName) : ILogger
{
private string _categoryName = categoryName;

public IDisposable? BeginScope<TState>(TState state) where TState : notnull
=> new PulseFlowLoggerScope<TState>(state);

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

outputHelper.WriteLine(new LogPulse(logLevel, eventId, exception, _categoryName, formatter.Invoke(state, exception), state as IReadOnlyList<KeyValuePair<string, object?>>).ToString());
outputHelper.WriteLine(new LogPulse(logLevel, eventId, exception, categoryName, formatter.Invoke(state, exception), state as IReadOnlyList<KeyValuePair<string, object?>>).ToString());
}
}
22 changes: 22 additions & 0 deletions Frank.Testing.Logging/SimpleTestLoggerProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Collections.Concurrent;

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

using Xunit.Abstractions;

namespace Frank.Testing.Logging;

public class SimpleTestLoggerProvider(ITestOutputHelper outputHelper, IOptionsMonitor<LoggerFilterOptions> options) : ILoggerProvider
{
private readonly ConcurrentDictionary<string, SimpleTestLogger> _loggers = new();

/// <inheritdoc />
public void Dispose()
{
_loggers.Clear();
}

/// <inheritdoc />
public ILogger CreateLogger(string categoryName) => _loggers.GetOrAdd(categoryName, new SimpleTestLogger(outputHelper, options.CurrentValue.MinLevel, categoryName));
}
21 changes: 21 additions & 0 deletions Frank.Testing.TestBases/Frank.Testing.TestBases.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="xunit.extensibility.core" Version="2.6.6" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Frank.Testing.Logging\Frank.Testing.Logging.csproj" />
</ItemGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
</Project>
43 changes: 43 additions & 0 deletions Frank.Testing.TestBases/HostApplicationTestBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Frank.Testing.Logging;

using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

using Xunit;
using Xunit.Abstractions;

namespace Frank.Testing.TestBases;

public abstract class HostApplicationTestBase : IAsyncLifetime
{
private readonly HostApplicationBuilder _hostApplicationBuilder;
private IHost? _host;
private readonly CancellationTokenSource _cancellationTokenSource = new();
private bool _initialized = false;

protected HostApplicationTestBase(ITestOutputHelper outputHelper, LogLevel logLevel = LogLevel.Information)
{
_hostApplicationBuilder = Host.CreateEmptyApplicationBuilder(new HostApplicationBuilderSettings());
_hostApplicationBuilder.Logging.AddSimpleTestLoggingProvider(outputHelper, logLevel);
}

public IServiceProvider Services => (_initialized ? _host?.Services : throw new InvalidOperationException("The host has not been initialized yet.")) ?? throw new InvalidOperationException("!!!");

protected virtual async Task SetupAsync(HostApplicationBuilder builder) => await Task.CompletedTask;

public async Task InitializeAsync()
{
await SetupAsync(_hostApplicationBuilder);
_host = _hostApplicationBuilder.Build();
await _host.StartAsync(_cancellationTokenSource.Token);
_initialized = true;
}

public async Task DisposeAsync()
{
await _cancellationTokenSource.CancelAsync();
await _host?.StopAsync()!;
await _host.WaitForShutdownAsync();
_host.Dispose();
}
}
46 changes: 46 additions & 0 deletions Frank.Testing.TestBases/WebHostApplicationTestBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Frank.Testing.Logging;

using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;

using Xunit;
using Xunit.Abstractions;

namespace Frank.Testing.TestBases;

public abstract class WebHostApplicationTestBase : IAsyncLifetime
{
private readonly IWebHostBuilder _hostApplicationBuilder;
private IWebHost? _host;
private readonly CancellationTokenSource _cancellationTokenSource = new();
private bool _initialized = false;

protected WebHostApplicationTestBase(ITestOutputHelper outputHelper, LogLevel logLevel = LogLevel.Information)
{
_hostApplicationBuilder = WebHost.CreateDefaultBuilder();
_hostApplicationBuilder.ConfigureLogging(logging => logging.AddSimpleTestLoggingProvider(outputHelper, logLevel));
}

public IServiceProvider Services => (_initialized ? _host?.Services : throw new InvalidOperationException("The host has not been initialized yet.")) ?? throw new InvalidOperationException("!!!");

protected virtual async Task SetupAsync(IWebHostBuilder builder) => await Task.CompletedTask;

protected HttpClient TestClient => (_initialized ? _host?.CreateTestClient() : throw new InvalidOperationException("The host has not been initialized yet.")) ?? throw new InvalidOperationException("!!!");
protected IEnumerable<string> GetServerEndpoints() => (_initialized ? _host?.GetServerEndpoints() : throw new InvalidOperationException("The host has not been initialized yet.")) ?? throw new InvalidOperationException("!!!");
public async Task InitializeAsync()
{
await SetupAsync(_hostApplicationBuilder);
_host = _hostApplicationBuilder.Build();
await _host.StartAsync(_cancellationTokenSource.Token);
_initialized = true;
}

public async Task DisposeAsync()
{
await _cancellationTokenSource.CancelAsync();
await _host?.StopAsync()!;
await _host.WaitForShutdownAsync();
_host.Dispose();
}
}
25 changes: 25 additions & 0 deletions Frank.Testing.TestBases/WebHostExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;

namespace Frank.Testing.TestBases;

public static class WebHostExtensions
{
public static HttpClient CreateTestClient(this IWebHost host)
{
var baseAddress = host.ServerFeatures.Get<IServerAddressesFeature>()?.Addresses.First();

return new HttpClient
{
BaseAddress = new Uri(baseAddress ?? throw new InvalidOperationException("The host has not been initialized yet."), UriKind.Absolute)
};
}

public static IEnumerable<string?> GetServerEndpoints(this IWebHost host)
{
return host.Services.GetServices<EndpointDataSource>().SelectMany(x => x.Endpoints).Select(x => x.DisplayName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,4 @@ public static void WriteCSharp<T>(this ITestOutputHelper outputHelper, IEnumerab
SortDirection = ListSortDirection.Ascending,
MaxDepth = 64,
};
}

public enum CSharpDumpType
{
Variable,
Class,
IEnumerable
}
4 changes: 2 additions & 2 deletions Frank.Testing.TestOutputExtensions/TestOutputXmlExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public static void WriteXml<T>(this ITestOutputHelper outputHelper, T source, Xm
using var textWriter = new StringWriter();
using var xmlWriter = XmlWriter.Create(textWriter, settings);
var xmlSerializer = new XmlSerializerFactory().CreateSerializer(typeof(T));
xmlSerializer.Serialize(xmlWriter, source);
xmlSerializer.Serialize(xmlWriter, source, new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty }));
outputHelper.WriteLine(textWriter.ToString());
}

Expand All @@ -24,6 +24,6 @@ public static void WriteXml<T>(this ITestOutputHelper outputHelper, T source, Xm
NewLineChars = "\n",
NewLineHandling = NewLineHandling.Replace,
OmitXmlDeclaration = false,
Encoding = new UTF8Encoding(false),
Encoding = new UTF8Encoding(false)
};
}

0 comments on commit 3747062

Please sign in to comment.