If you are running integration or component tests with xUnit or other runner, having logs in the test console is helpful for:
- Debugging: Quick identification and resolution of issues during testing.
- Traceability: Easier to follow the flow of events leading up to issues.
- Convenience: Accessing logs directly in the test output streamlines the development process.
- Real-time Monitoring: Watching test execution and behavior in real-time.
- Simplicity in CI/CD Pipelines: Facilitating log collection and analysis in automated pipelines.
Quick guide
First implement define the XUnitLogger class that implements build in ILogger class.
internal class XunitLogger : ILogger
{
private readonly ITestOutputHelper _output;
private readonly LogLevel _minLogLevel;
private readonly string _categoryName;
public XunitLogger(ITestOutputHelper output, LogLevel minLogLevel, string categoryName)
{
_output = output;
_minLogLevel = minLogLevel;
_categoryName = categoryName;
}
public IDisposable? BeginScope<TState>(TState state) where TState : notnull
{
throw new NotImplementedException();
}
public bool IsEnabled(LogLevel logLevel) => logLevel >= _minLogLevel;
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
if (!IsEnabled(logLevel))
return;
_output.WriteLine($"{logLevel} - {_categoryName} - {formatter(state, exception)}");
}
}
ILoggerFactory Class: Create a factory for generating ILogger instances, tailored for xUnit.
class XUnitLoggingFactory : ILoggerFactory
{
private readonly ITestOutputHelper _output;
private readonly LogLevel _minLogLevel;
public XUnitLoggingFactory(ITestOutputHelper output, LogLevel minLogLevel)
{
_output = output;
_minLogLevel = minLogLevel;
}
public void AddProvider(ILoggerProvider provider) => throw new NotSupportedException();
public ILogger CreateLogger(string categoryName) => new XunitLogger(_output, _minLogLevel, categoryName);
public void Dispose() { }
}
Create an extension method that will override the ILoggerFactory with the one for testing.
static class LoggingExtensions
{
internal static IWebHostBuilder UseXunitLogging(this IWebHostBuilder builder, ITestOutputHelper output, LogLevel minLevel = LogLevel.Error) => builder.ConfigureTestServices(services =>
{
services.RemoveAll(typeof(ILoggerFactory));
services.AddSingleton<ILoggerFactory>(new XUnitLoggingFactory(output, minLevel));
});
}
Then the integration test will look like this:
public class ApplicationntegrationTests
{
private readonly WebJobApplicationFactory _factory;
public ApplicationntegrationTests(ITestOutputHelper output)
{
_factory = new WebJobApplicationFactory()
.WithWebHostBuilder(builder => builder
.UseXunitLogging(output));
}
In the test runner, this is how will look the output:
One thing to notice is that the output will be displayed only once when the test fully finishes.
Conclusions
This approach replaces the standard application logging with xUnit-specific logging. If you prefer not to override but just want to capture logs in xUnit, consider using ILoggerProvider instead.
This streamlined method ensures efficient and clear logging in your integration or component tests using xUnit.