generated from frankhaugen/DotnetRepoTemplate
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add testing loggers and extensions for xUnit.
This commit introduces various logging infrastructure for testing purposes. It includes logging extensions for xUnit that create test logger instances and a test logger factory using the ITestOutputHelper from Xunit. Frank.PulseFlow has been used to send and handle logging output flow, improving the readability and organization of test logs.
- Loading branch information
1 parent
1b4670d
commit 627fb4d
Showing
18 changed files
with
450 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
namespace Frank.Testing.Logging; | ||
|
||
internal static class DictionaryExtensions | ||
{ | ||
public static TValue GetOrCreate<TKey, TValue>( | ||
this IDictionary<TKey, TValue> dictionary, | ||
TKey key, | ||
Func<TValue> valueFactory) | ||
{ | ||
if (dictionary.TryGetValue(key, out TValue? value) && value != null) | ||
return value; | ||
|
||
value = valueFactory(); | ||
dictionary[key] = value; | ||
return value; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<Description>Loggers for the Microsoft.Extensions.Logging framework wrapping the xUnit.net ITestOutputHelper.</Description> | ||
<PackageTags>xunit, test, testing, tests, runner, console, logger, logging, output, helper, itestoutputhelper, dependency, injection, di, ioc, microsoft, extensions, frank, haugen</PackageTags> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Frank.PulseFlow.Logging" Version="1.5.0" /> | ||
<PackageReference Include="Frank.Reflection" Version="1.0.0" /> | ||
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" /> | ||
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" /> | ||
<PackageReference Include="xunit.extensibility.core" Version="2.6.4" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\Frank.Testing.TestOutputExtensions\Frank.Testing.TestOutputExtensions.csproj" /> | ||
</ItemGroup> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
using Frank.PulseFlow; | ||
using Frank.PulseFlow.Logging; | ||
|
||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace Frank.Testing.Logging; | ||
|
||
public static class LoggingBuilderExtensions | ||
{ | ||
/// <summary> | ||
/// Adds test logging to the ILoggingBuilder. | ||
/// </summary> | ||
/// <param name="builder">The ILoggingBuilder to add the test logging to.</param> | ||
/// <param name="outputHelper">The ITestOutputHelper to redirect the logging output to.</param> | ||
Check warning on line 15 in Frank.Testing.Logging/LoggingBuilderExtensions.cs GitHub Actions / Merge Job / Publish Preview Job
|
||
/// <param name="logLevel">The log level to use for the test logging. Default is LogLevel.Debug.</param> | ||
Check warning on line 16 in Frank.Testing.Logging/LoggingBuilderExtensions.cs GitHub Actions / Merge Job / Publish Preview Job
|
||
/// <returns>The modified ILoggingBuilder with the test logging added.</returns> | ||
public static ILoggingBuilder AddPulseFlowTestLoggingProvider(this ILoggingBuilder builder) | ||
{ | ||
builder.AddPulseFlow(); | ||
builder.Services.AddSingleton<IFlow, TestLoggingOutputFlow>(); | ||
return builder; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
using Frank.PulseFlow; | ||
using Frank.PulseFlow.Logging; | ||
|
||
using Microsoft.Extensions.Logging; | ||
|
||
namespace Frank.Testing.Logging; | ||
|
||
public class PulseFlowTestLogger(IConduit conduit) : ILogger | ||
{ | ||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) | ||
=> conduit.SendAsync(new LogPulse<TState>(logLevel, eventId, state, exception, formatter, "TestLogger")).GetAwaiter().GetResult(); | ||
|
||
public bool IsEnabled(LogLevel logLevel) => true; | ||
|
||
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => new PulseFlowLoggerScope<TState>(state); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Logging; | ||
|
||
using Xunit.Abstractions; | ||
|
||
namespace Frank.Testing.Logging; | ||
|
||
public static class ServiceCollectionExtensions | ||
{ | ||
/// <summary> | ||
/// Adds 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 AddTestLogging(this IServiceCollection services, ITestOutputHelper outputHelper, LogLevel logLevel = LogLevel.Debug) | ||
{ | ||
services.AddLogging(builder => builder.AddPulseFlowTestLoggingProvider()); | ||
return services; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
using Microsoft.Extensions.Logging; | ||
|
||
using Xunit.Abstractions; | ||
|
||
namespace Frank.Testing.Logging; | ||
|
||
public class TestLogger<T> : ILogger<T> | ||
{ | ||
public ITestOutputHelper OutputHelper { get; } | ||
public LogLevel LogLevel { get; } | ||
public string? CategoryName { get; } | ||
|
||
public TestLogger(ITestOutputHelper outputHelper, LogLevel logLevel, string? categoryName = null) | ||
{ | ||
OutputHelper = outputHelper; | ||
LogLevel = logLevel; | ||
CategoryName = categoryName; | ||
} | ||
|
||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) | ||
{ | ||
if (logLevel < LogLevel) | ||
return; | ||
|
||
OutputHelper.WriteLine($"{logLevel}: {formatter(state, exception)}"); | ||
} | ||
|
||
public bool IsEnabled(LogLevel logLevel) | ||
{ | ||
return logLevel >= LogLevel; | ||
} | ||
|
||
public IDisposable? BeginScope<TState>(TState state) where TState : notnull | ||
{ | ||
return new TestLoggerScope<TState>(state); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
namespace Frank.Testing.Logging; | ||
|
||
public class TestLoggerScope<TState>(TState state) : IDisposable | ||
{ | ||
public TState? State { get; private set; } = state; | ||
|
||
public void Dispose() => State = default; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
using Frank.PulseFlow; | ||
using Frank.PulseFlow.Logging; | ||
|
||
using Xunit.Abstractions; | ||
|
||
namespace Frank.Testing.Logging; | ||
|
||
/// <summary> | ||
/// Represents a class that handles logging output flow. | ||
/// </summary> | ||
public class TestLoggingOutputFlow(ITestOutputHelper outputHelper) : IFlow | ||
{ | ||
public Task HandleAsync(IPulse pulse, CancellationToken cancellationToken) | ||
{ | ||
outputHelper.WriteLine(pulse.ToString()); | ||
return Task.CompletedTask; | ||
} | ||
|
||
public bool CanHandle(Type pulseType) => pulseType == typeof(LogPulse<>); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
using Frank.PulseFlow.Logging; | ||
using Frank.Reflection; | ||
|
||
using Microsoft.Extensions.Logging; | ||
|
||
using Xunit.Abstractions; | ||
|
||
namespace Frank.Testing.Logging; | ||
|
||
public static class TestOutputHelperExtensions | ||
{ | ||
/// <summary> | ||
/// Creates a test logger instance with the specified log category name. | ||
/// </summary> | ||
/// <typeparam name="T">The type of the class to which the created logger will be associated.</typeparam> | ||
/// <param name="outputHelper">The ITestOutputHelper instance used for logging.</param> | ||
/// <param name="categoryName">The name of the log category. (optional)</param> | ||
/// <param name="logLevel"></param> | ||
/// <returns>An instance of <see cref="ILogger{T}"/> that can be used for logging tests.</returns> | ||
public static ILogger<T> CreateTestLogger<T>(this ITestOutputHelper outputHelper, LogLevel logLevel = LogLevel.Debug) | ||
=> new TestLogger<T>(outputHelper, logLevel, typeof(T).GetDisplayName()); | ||
|
||
/// <summary> | ||
/// Creates a test logger factory using the specified <paramref name="outputHelper"/>. | ||
/// </summary> | ||
/// <param name="outputHelper">The test output helper.</param> | ||
/// <param name="logLevel"></param> | ||
/// <returns>A new instance of <see cref="ILoggerFactory"/> for testing purposes.</returns> | ||
public static ILoggerFactory CreateTestLoggerFactory(this ITestOutputHelper outputHelper, LogLevel logLevel = LogLevel.Debug) | ||
{ | ||
var factory = new LoggerFactory(); | ||
// factory.AddProvider(new PulseFlowLoggerProvider()); | ||
return factory; | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
Frank.Testing.TestOutputExtensions/Frank.Testing.TestOutputExtensions.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<RootNamespace>Xunit</RootNamespace> | ||
<Description>Extends ITestOutputHelper to allow output of a generic type using a serializer.</Description> | ||
<PackageTags>test, xunit, output, helper, serializer, json, console</PackageTags> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="FluentAssertions" Version="6.12.0" /> | ||
<PackageReference Include="xunit.abstractions" Version="2.0.3" /> | ||
<PackageReference Include="Xunit.Extensions.Ordering" Version="1.4.5" /> | ||
</ItemGroup> | ||
|
||
</Project> |
32 changes: 32 additions & 0 deletions
32
Frank.Testing.TestOutputExtensions/TestOutputExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
using System.Text.Json; | ||
using System.Text.Json.Serialization; | ||
|
||
using Xunit.Abstractions; | ||
|
||
namespace Xunit; | ||
|
||
public static class TestOutputExtensions | ||
{ | ||
/// <summary> | ||
/// Writes the specified object's string representation followed by the current line terminator to the output. | ||
/// </summary> | ||
/// <typeparam name="T">The type of the object to write.</typeparam> | ||
/// <param name="outputHelper">The ITestOutputHelper instance.</param> | ||
/// <param name="source">The object to write.</param> | ||
/// <exception cref="System.Text.Json.JsonException">Thrown when unable to serialize the object.</exception> | ||
public static void WriteLine<T>(this ITestOutputHelper outputHelper, T? source) => outputHelper.WriteLine(JsonSerializer.Serialize(source, JsonSerializerOptions)); | ||
|
||
/// <summary> | ||
/// Writes a JSON representation of the specified object to the output helper. | ||
/// </summary> | ||
/// <typeparam name="T">The type of object to be serialized.</typeparam> | ||
/// <param name="outputHelper">The output helper to write the JSON to.</param> | ||
/// <param name="source">The object to be serialized.</param> | ||
/// <param name="options">The options to be used for the serialization (optional).</param> | ||
/// <remarks> | ||
/// If <paramref name="options"/> is not provided, the default <see cref="JsonSerializerOptions"/> will be used. | ||
/// </remarks> | ||
public static void WriteJson<T>(this ITestOutputHelper outputHelper, T? source, JsonSerializerOptions? options = null) => outputHelper.WriteLine(options == null ? JsonSerializer.Serialize(source, JsonSerializerOptions) : JsonSerializer.Serialize(source, options)); | ||
|
||
private static JsonSerializerOptions JsonSerializerOptions => new() { Converters = { new JsonStringEnumConverter() }, WriteIndented = true, ReferenceHandler = ReferenceHandler.IgnoreCycles, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
|
||
<IsPackable>false</IsPackable> | ||
<IsTestProject>true</IsTestProject> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" /> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" /> | ||
<PackageReference Include="xunit" Version="2.6.4" /> | ||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6"> | ||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||
<PrivateAssets>all</PrivateAssets> | ||
</PackageReference> | ||
<PackageReference Include="coverlet.collector" Version="6.0.0"> | ||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||
<PrivateAssets>all</PrivateAssets> | ||
</PackageReference> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\Frank.Testing.Logging\Frank.Testing.Logging.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
global using Xunit; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
using JetBrains.Annotations; | ||
|
||
using Xunit.Abstractions; | ||
|
||
namespace Frank.Testing.Tests; | ||
|
||
[TestSubject(typeof(TestOutputExtensions))] | ||
public class TestOutputExtensionsTests | ||
{ | ||
private readonly ITestOutputHelper _outputHelper; | ||
|
||
public TestOutputExtensionsTests(ITestOutputHelper outputHelper) | ||
{ | ||
_outputHelper = outputHelper; | ||
} | ||
|
||
[Fact] | ||
public void Test1() | ||
{ | ||
var model = new TestModel { Name = "Frank" }; | ||
_outputHelper.WriteLine(model); | ||
} | ||
|
||
[Fact] | ||
public void Test2() | ||
{ | ||
var model = new TestModel { Name = "Frank" }; | ||
_outputHelper.WriteJson(model); | ||
} | ||
|
||
private class TestModel | ||
{ | ||
public string? Name { get; set; } | ||
} | ||
} |
Oops, something went wrong.