xUnit has changed in version 2 to no longer capture the standard output for tests:
If you used xUnit.net 1.x, you may have previously been writing output to Console
, Debug
, or Trace
. When xUnit.net v2 shipped with parallelization turned on by default, this output capture mechanism was no longer appropriate; it is impossible to know which of the many tests that could be running in parallel were responsible for writing to those shared resources.
Instead, you are now supposed to use an explicit mechanism to write to the test output. Basically, instead of writing to the console, you are writing to a special ITestOutputHelper
.
Of course, this output mechanism is not supported by default with ASP.NET Core logging. Fortunately, writing a logging provider for the test output isn’t too difficult. I’ve just implemented a quick provider and included it in my answer below. You can use it like this:
public class Example
{
private readonly ILogger<Example> _logger;
public Example(ITestOutputHelper testOutputHelper)
{
var loggerFactory = new LoggerFactory();
loggerFactory.AddProvider(new XunitLoggerProvider(testOutputHelper));
_logger = loggerFactory.CreateLogger<Example>();
}
[Fact]
public void Test()
{
_logger.LogDebug("Foo bar baz");
}
}
Note that you usually should avoid creating a complete dependency injection container in unit tests. Having DI in unit tests is usually a sign that your unit test is not a unit test but instead an integration test. In a unit test, you should only test one particular unit and pass all its dependencies explicitly—more than often just as a mock. As you can see in the example above, creating a logger is actually a very simple thing to do without DI.
As promised, this is the XunitLoggerProvider
and the XunitLogger
which you need to run the code shown above, and to integrate the Microsoft.Extensions.Logging
framework with xUnit test output:
public class XunitLoggerProvider : ILoggerProvider
{
private readonly ITestOutputHelper _testOutputHelper;
public XunitLoggerProvider(ITestOutputHelper testOutputHelper)
{
_testOutputHelper = testOutputHelper;
}
public ILogger CreateLogger(string categoryName)
=> new XunitLogger(_testOutputHelper, categoryName);
public void Dispose()
{ }
}
public class XunitLogger : ILogger
{
private readonly ITestOutputHelper _testOutputHelper;
private readonly string _categoryName;
public XunitLogger(ITestOutputHelper testOutputHelper, string categoryName)
{
_testOutputHelper = testOutputHelper;
_categoryName = categoryName;
}
public IDisposable BeginScope<TState>(TState state)
=> NoopDisposable.Instance;
public bool IsEnabled(LogLevel logLevel)
=> true;
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
_testOutputHelper.WriteLine($"{_categoryName} [{eventId}] {formatter(state, exception)}");
if (exception != null)
_testOutputHelper.WriteLine(exception.ToString());
}
private class NoopDisposable : IDisposable
{
public static NoopDisposable Instance = new NoopDisposable();
public void Dispose()
{ }
}
}
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…