Neovolve.Logging.Xunit
A Microsoft.Extensions.Logging provider for xUnit test output
Install / Use
/learn @roryprimrose/Neovolve.Logging.XunitREADME
Introduction
Neovolve.Logging.Xunit is a NuGet package that returns an ILogger or ILogger<T> that wraps around the ITestOutputHelper supplied by xUnit. xUnit uses this helper to write log messages to the test output of each test execution. This means that any log messages from classes being tested will end up in the xUnit test result output.
Neovolve.Logging.Xunit.Signed.v3
Beta builds are available on MyGet.
- Installation
- Usage
- Output Formatting
- Inspection
- Configured LoggerFactory
- Existing Loggers
- Sensitive Values
- Configuration
Installation
Using xUnit v3
Run the following in the NuGet command line or visit the NuGet package page.
Install-Package Neovolve.Logging.Xunit.v3
If you need a strong named version of this library, run the following in the NuGet command line or visit the NuGet package page.
Install-Package Neovolve.Logging.Xunit.Signed.v3
[Back to top][0]
Using xUnit v2
Run the following in the NuGet command line or visit the NuGet package page.
Install-Package Neovolve.Logging.Xunit
If you need a strong named version of this library, run the following in the NuGet command line or visit the NuGet package page.
Install-Package Neovolve.Logging.Xunit.Signed
[Back to top][0]
Usage
The common usage of this package is to call the BuildLogger<T> extension method on the xUnit ITestOutputHelper.
Consider the following example of a class to test.
using System;
using Microsoft.Extensions.Logging;
public class MyClass
{
private readonly ILogger _logger;
public MyClass(ILogger<MyClass> logger)
{
_logger = logger;
}
public string DoSomething()
{
_logger.LogInformation("Hey, we did something");
return Guid.NewGuid().ToString();
}
}
Call BuildLoggerFor<T>() on ITestOutputHelper to generate the ILogger<T> to inject into the class being tested.
public class MyClassTests
{
private readonly ITestOutputHelper _output;
public MyClassTests(ITestOutputHelper output)
{
_output = output;
}
[Fact]
public void DoSomethingReturnsValue()
{
using var logger = output.BuildLoggerFor<MyClass>();
var sut = new MyClass(logger);
var actual = sut.DoSomething();
// The xUnit test output should now include the log message from MyClass.DoSomething()
actual.Should().NotBeNull();
}
}
This would output the following in the test results.
Information [0]: Hey, we did something
Similarly, using the BuildLogger() extension method will return an ILogger configured with xUnit test output.
The above examples inline the declaration of the logger with using var to ensure that the logger instance (and internal ILoggerFactory) is disposed.
You can avoid having to build the logger instance in each unit test method by deriving the test class from either LoggingTestsBase or LoggingTestsBase<T>. These classes provide the implementation to build the logger and dispose it. They also provide access to the ITestOutputHelper instance for writing directly to the test output.
public class MyClassTests : LoggingTestsBase<MyClass>
{
public MyClassTests(ITestOutputHelper output) : base(output, LogLevel.Information)
{
}
[Fact]
public void DoSomethingReturnsValue()
{
var sut = new MyClass(Logger);
var actual = sut.DoSomething();
// The xUnit test output should now include the log message from
MyClass.DoSomething()
Output.WriteLine("This works too");
actual.Should().NotBeNullOrWhiteSpace();
}
}
The BuildLogger and BuildLoggerFor<T> extension methods along with the LoggingTestsBase and LoggingTestsBase<T> abstract classes also provide overloads to set the logging level or define
[logging configuration][7].
[Back to top][0]
Output Formatting
The default formatting to the xUnit test results may not be what you want. You can define your own ILogFormatter class to control how the output looks. There is a configurable formatter for standard messages and another configurable formatter for scope start and end messages.
public class MyFormatter : ILogFormatter
{
public string Format(
int scopeLevel,
string categoryName,
LogLevel logLevel,
EventId eventId,
string message,
Exception exception)
{
var builder = new StringBuilder();
if (scopeLevel > 0)
{
builder.Append(' ', scopeLevel * 2);
}
builder.Append($"{logLevel} ");
if (!string.IsNullOrEmpty(categoryName))
{
builder.Append($"{categoryName} ");
}
if (eventId.Id != 0)
{
builder.Append($"[{eventId.Id}]: ");
}
if (!string.IsNullOrEmpty(message))
{
builder.Append(message);
}
if (exception != null)
{
builder.Append($"\n{exception}");
}
return builder.ToString();
}
}
public class MyConfig : LoggingConfig
{
public MyConfig()
{
base.Formatter = new MyFormatter();
}
public static MyConfig Current { get; } = new MyConfig();
}
The custom ILogFormatter is defined on a LoggingConfig class that can be provided when creating a logger. The MyConfig.Current property above is there provide a clean way to share the config across test classes.
using System;
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Xunit;
using Xunit;
public class MyClassTests
{
private readonly ITestOutputHelper _output;
public MyClassTests(ITestOutputHelper output)
{
_output = output;
}
[Fact]
public void DoSomethingReturnsValue()
{
using var logger = _output.BuildLogger(MyConfig.Current);
var sut = new MyClass(logger);
var actual = sut.DoSomething();
// The xUnit test output should now include the log message from MyClass.DoSomething()
actual.Should().NotBeNull();
}
}
In the same way the format of start and end scope messages can be formatted by providing a custom formatter on LoggingConfig.ScopeFormatter.
[Back to top][0]
Inspection
Using this library makes it really easy to output log messages from your code as part of the test results. You may want to also inspect the log messages written as part of the test assertions as well.
The BuildLogger and BuildLoggerFor<T> extension methods support this by returning a ICacheLogger or ICacheLogger<T> respectively. The cache logger is a wrapper around the created logger and exposes all the log entries written by the test.
using System;
using Neovolve.Logging.Xunit;
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Xunit;
using Xunit;
public class MyClassTests
{
private readonly ITestOutputHelper _output;
public MyClassTests(ITestOutputHelper output)
{
_output = output;
}
[Fact]
public void DoSomethingReturnsValue()
{
using var logger = _output.BuildLogger();
var sut = new MyClass(logger);
sut.DoSomething();
logger.Count.Should().Be(1);
logger.Entries.Should().HaveCount(1);
logger.Last.Message.Should().Be("Hey, we did something");
}
}
Perhaps you don't want to use the xUnit ITestOutputHelper but still want to use the ICacheLogger to run assertions over log messages written by the class under test. You can do this by creating a CacheLogger or CacheLogger<T> directly.
using System;
using Neovolve.Logging.Xunit;
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Xunit;
public class MyClassTests
{
[Fact]
public void DoSomethingReturnsValue()
{
var logger = new CacheLogger();
var sut = new MyClass(logger);
sut.DoSomething();
logger.Count.Should().Be(1);
logger.Entries.Should().HaveCount(1);
logger.Last.Message.Should().Be("Hey, we did something");
}
}
The ICacheLogger interface also supports a LogWritten event where `Lo
