Logging BDDfy scenarios in .NET Core and xUnit2

How to capture your log output during tests when using Serilog and/or BDDfy

Logging BDDfy scenarios in .NET Core and xUnit2

For BDD testing in .NET Core, I recommend TestStack.BDDfy.

When using BBD scernaios in xUnit2 you expect the following:

As a store owner
In order to keep track of stock
I want to add items back to stock when they're returned.

Scenario 1: Refunded items should be returned to stock
Given that a customer previously bought a black sweater from me
  And I have three black sweaters in stock.
When he returns the black sweater for a refund
Then I should have four black sweaters in stock.

Scenario 2: Replaced items should be returned to stock
Given that a customer previously bought a blue garment from me
  And I have two blue garments in stock
  And three black garments in stock.
When he returns the blue garment for a replacement in black
Then I should have three blue garments in stock
  And two black garments in stock.

However, unfortunately as xUnit runs in parallel you may end up with the scenarios intertwined as the default BDDfy logger writes to the console as discussed in Console report not working with xUnit 2 and NUnit 3.

The solution/s reached in the thread either require modifying the BDDfy logger to make it threadsafe (and thus taking ownership of the code) or using a community extension that implements a custom xUnit test runner which can limit your overall testing approach.

I did not like either solution. :/

  1. Create a base class for your tests. As your about to learn (if you didn't know already) is that xUnit2 uses an interface ITestOutputHelper to isolate logs between tests.

    public class SerilogTestBase : IDisposable
        protected SerilogTestBase(ITestOutputHelper outputHelper)
            _logger = LogHelper.Capture(outputHelper);
        public static ILogger Log
                LogHelper.TryGetLogger(out ILogger log);
                return log;
        public virtual void Dispose()
        private readonly IDisposable _logger;

    The key part of the above is the LogHelper.Capture that takes the ITestOutputHelper and tracks it across threads using a correlation Guid. See the full implementation on GitHub.

    private static readonly AsyncLocal<Guid> TestCorrelationId = new AsyncLocal<Guid>();
    static readonly ConcurrentDictionary<Guid, ITestOutputHelper> LoggerLookup = new ConcurrentDictionary<Guid, ITestOutputHelper>();
    public static bool TryGetTestOutputHelper(out ITestOutputHelper testOutputHelper)
        return LoggerLookup.TryGetValue(TestCorrelationId.Value, out testOutputHelper);
  2. Following the above approach, it is simple to extend the BDDfy TextReporter

    internal class XUnitBddfyTextReporter : IProcessor
        public void Process(Story story)
            if (!LogHelper.TryGetTestOutputHelper(out ITestOutputHelper logger))
            var reporter = new TextReporter();
        public ProcessType ProcessType => ProcessType.Report;