Divergic.Logging.Xunit 4.3.1

Suggested Alternatives

Neovolve.Logging.Xunit

Additional Details

This package has been renamed to Neovolve.Logging.Xunit

dotnet add package Divergic.Logging.Xunit --version 4.3.1
NuGet\Install-Package Divergic.Logging.Xunit -Version 4.3.1
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Divergic.Logging.Xunit" Version="4.3.1" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Divergic.Logging.Xunit --version 4.3.1
#r "nuget: Divergic.Logging.Xunit, 4.3.1"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Divergic.Logging.Xunit as a Cake Addin
#addin nuget:?package=Divergic.Logging.Xunit&version=4.3.1

// Install Divergic.Logging.Xunit as a Cake Tool
#tool nuget:?package=Divergic.Logging.Xunit&version=4.3.1

Package rename

Note: This package is being renamed to Neovolve.Logging.Xunit.

Introduction

Divergic.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.

GitHub license Nuget Nuget Actions Status

Installation

Run the following in the NuGet command line or visit the NuGet package page.

Install-Package Divergic.Logging.Xunit

Back to top

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.

Back to top

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.Abstractions;

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

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 Divergic.Logging.Xunit;
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Xunit;
using Xunit.Abstractions;

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 Divergic.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");
    }
}

Back to top

Configured LoggerFactory

You may have an integration or acceptance test that requires additional configuration to the log providers on ILoggerFactory while also supporting the logging out to xUnit test results. You can do this by create a factory that is already configured with xUnit support.

You can get an xUnit configured ILoggerFactory by calling output.BuildLoggerFactory().

using System;
using Divergic.Logging.Xunit;
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Xunit;
using Xunit.Abstractions;

public class MyClassTests
{
    private readonly ILogger _logger;

    public MyClassTests(ITestOutputHelper output)
    {
        var factory = output.BuildLoggerFactory();

        // call factory.AddConsole or other provider extension method

        _logger = factory.CreateLogger(nameof(MyClassTests));
    }

    [Fact]
    public void DoSomethingReturnsValue()
    {
        var sut = new MyClass(_logger);

        // The xUnit test output should now include the log message from MyClass.DoSomething()

        var actual = sut.DoSomething();

        actual.Should().NotBeNullOrWhiteSpace();
    }
}

The BuildLoggerFactory extension methods provide overloads to set the logging level or define logging configuration.

Back to top

Existing Loggers

Already have an existing logger and want the above cache support? Got you covered there too using the WithCache() method.

using System;
using Divergic.Logging.Xunit;
using FluentAssertions;
using Microsoft.Extensions.Logging;
using NSubstitute;
using Xunit;

public class MyClassTests
{
    [Fact]
    public void DoSomethingReturnsValue()
    {
        var logger = Substitute.For<ILogger>();

        logger.IsEnabled(Arg.Any<LogLevel>()).Returns(true);

        var cacheLogger = logger.WithCache();

        var sut = new MyClass(cacheLogger);

        sut.DoSomething();

        cacheLogger.Count.Should().Be(1);
        cacheLogger.Entries.Should().HaveCount(1);
        cacheLogger.Last.Message.Should().Be("Hey, we did something");
    }
}

The WithCache() also supports ILogger<T>.

using System;
using Divergic.Logging.Xunit;
using FluentAssertions;
using Microsoft.Extensions.Logging;
using NSubstitute;
using Xunit;

public class MyClassTests
{
    [Fact]
    public void DoSomethingReturnsValue()
    {
        var logger = Substitute.For<ILogger<MyClass>>();

        logger.IsEnabled(Arg.Any<LogLevel>()).Returns(true);

        var cacheLogger = logger.WithCache();

        var sut = new MyClass(cacheLogger);

        sut.DoSomething();

        cacheLogger.Count.Should().Be(1);
        cacheLogger.Entries.Should().HaveCount(1);
        cacheLogger.Last.Message.Should().Be("Hey, we did something");
    }
}

Back to top

Sensitive Values

The LoggingConfig class exposes a SensitiveValues property that holds a collection of strings. All sensitive values found in a log message or a start/end scope message will be masked out.

public class ScopeScenarioTests : LoggingTestsBase<ScopeScenarioTests>
{
    private static readonly LoggingConfig _config = new LoggingConfig().Set(x => x.SensitiveValues.Add("secret"));

    public ScopeScenarioTests(ITestOutputHelper output) : base(output, _config)
    {
    }

    [Fact]
    public void TestOutputWritesScopeBoundariesUsingObjectsWithSecret()
    {
        Logger.LogCritical("Writing critical message with secret");
        Logger.LogDebug("Writing debug message with secret");
        Logger.LogError("Writing error message with secret");
        Logger.LogInformation("Writing information message with secret");
        Logger.LogTrace("Writing trace message with secret");
        Logger.LogWarning("Writing warning message with secret");

        var firstPerson = Model.Create<StructuredData>().Set(x => x.Email = "secret");

        using (Logger.BeginScope(firstPerson))
        {
            Logger.LogInformation("Inside first scope with secret");

            var secondPerson = Model.Create<StructuredData>().Set(x => x.FirstName = "secret");

            using (Logger.BeginScope(secondPerson))
            {
                Logger.LogInformation("Inside second scope with secret");
            }

            Logger.LogInformation("After second scope with secret");
        }

        Logger.LogInformation("After first scope with secret");
    }

The above test will render the following to the test output.

Critical [0]: Writing critical message with ****
Debug [0]: Writing debug message with ****
Error [0]: Writing error message with ****
Information [0]: Writing information message with ****
Trace [0]: Writing trace message with ****
Warning [0]: Writing warning message with ****
<Scope 1>
   Scope data: 
   {
     "DateOfBirth": "1972-10-07T16:35:31.2039449Z",
     "Email": "****",
     "FirstName": "Amos",
     "LastName": "Burton"
   }
   Information [0]: Inside first scope with ****
      <Scope 2>
      Scope data: 
      {
        "DateOfBirth": "1953-07-04T06:55:31.2333376Z",
        "Email": "james.holden@rocinante.space",
        "FirstName": "****",
        "LastName": "Holden"
      }
      Information [0]: Inside second scope with ****
   </Scope 2>
   Information [0]: After second scope with ****
</Scope 1>
Information [0]: After first scope with ****

Back to top

Configuration

Logging configuration can be controled by using a LoggingConfig class as indicated in the Output Formatting section above. The following are the configuration options that can be set.

Formatter: Defines a custom formatter for rendering log messages to xUnit test output.

ScopeFormatter: Defines a custom formatter for rendering start and end scope messages to xUnit test output.

IgnoreTestBoundaryException: Defines whether exceptions thrown while logging outside of the test execution will be ignored.

LogLevel: Defines the minimum log level that will be written to the test output. This helps to limit the noise in test output when set to higher levels. Defaults to LogLevel.Trace.

ScopePaddingSpaces: Defines the number of spaces to use for indenting scopes.

SensitiveValues: Defines a collection of sensitive values that will be masked in the test output logging.

Back to top

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (6)

Showing the top 5 NuGet packages that depend on Divergic.Logging.Xunit:

Package Downloads
AutoMoqFixture

Package Description

U2U.EntityFrameworkCore.Testing

Package Description

InsonusK.Xunit.ExpectationsTest

Base class of Xunit test expectation methods support

Reductech.Sequence.Core.TestHarness

Class library for automatically testing Sequence® Core steps.

HerrGeneral.Test.Extension

Helper methods for HerrGeneral testing

GitHub repositories (9)

Showing the top 5 popular GitHub repositories that depend on Divergic.Logging.Xunit:

Repository Stars
JasperFx/marten
.NET Transactional Document DB and Event Store on PostgreSQL
asynkron/protoactor-dotnet
Proto Actor - Ultra fast distributed actors for Go, C# and Java/Kotlin
zoriya/Kyoo
A portable and vast media library solution.
imperugo/StackExchange.Redis.Extensions
Azure/Industrial-IoT
Azure Industrial IoT Platform
Version Downloads Last updated
4.3.1 992 4/20/2024
4.3.0 208,790 11/21/2023
4.2.0 1,418,563 8/9/2022
4.2.0-beta0001 166 8/9/2022
4.1.0 129,987 6/23/2022
4.1.0-beta0002 175 6/23/2022
4.1.0-beta0001 175 6/22/2022
4.0.0 491,100 1/10/2022
3.6.1-beta0010 207 1/10/2022
3.6.0 1,127,607 11/13/2020
3.5.2-beta0019 330 11/13/2020
3.5.2-beta0018 323 11/13/2020
3.5.2-beta0017 318 11/13/2020
3.5.2-beta0016 324 11/13/2020
3.5.2-beta0015 368 11/12/2020
3.5.2-beta0010 327 11/12/2020
3.5.2-beta0006 364 11/10/2020
3.5.2-beta0003 738 10/14/2020
3.5.2-beta0001 14,118 10/12/2020
3.5.1 132,520 10/8/2020
3.5.1-releasejob0001 532 8/4/2020
3.5.1-beta0032 416 10/8/2020
3.5.1-beta0031 350 10/8/2020
3.5.1-beta0030 364 10/8/2020
3.5.1-beta0029 433 9/27/2020
3.5.1-beta0028 367 9/15/2020
3.5.1-beta0027 373 9/9/2020
3.5.1-beta0026 367 9/9/2020
3.5.1-beta0025 372 9/2/2020
3.5.1-beta0024 359 8/21/2020
3.5.1-beta0023 405 8/15/2020
3.5.1-beta0022 410 8/12/2020
3.5.1-beta0021 351 8/12/2020
3.5.1-beta0020 379 8/11/2020
3.5.1-beta0019 406 8/8/2020
3.5.1-beta0018 414 8/8/2020
3.5.1-beta0017 421 8/8/2020
3.5.1-beta0015 391 8/5/2020
3.5.1-beta0013 365 8/4/2020
3.5.1-beta0012 371 8/4/2020
3.5.1-beta0011 383 8/4/2020
3.5.1-beta0010 390 8/2/2020
3.5.1-beta0009 417 7/31/2020
3.5.1-beta0008 420 7/31/2020
3.5.1-beta0007 424 7/31/2020
3.5.1-beta0003 428 7/31/2020
3.5.0 176,750 6/22/2020
3.5.0-beta0001 393 6/17/2020
3.4.0 108,462 4/28/2020
3.3.1-beta0004 390 4/28/2020
3.3.0 61,206 3/18/2020
3.3.0-beta0005 480 3/18/2020
3.2.2-beta0003 2,779 1/12/2020
3.2.2-beta0002 454 1/12/2020
3.2.2-beta0001 450 1/12/2020
3.2.1 219,618 12/18/2019
3.2.1-beta0001 479 12/18/2019
3.2.0 675 12/18/2019
3.2.0-beta0002 471 12/18/2019
3.1.0 196,459 9/27/2019
3.1.0-beta0002 443 9/27/2019
3.0.0 68,291 5/3/2019
2.2.0 47,845 4/18/2019
2.2.0-beta0001 500 4/18/2019
2.1.0 1,796 4/14/2019
2.1.0-beta0003 517 4/14/2019
2.1.0-beta0002 486 4/13/2019
2.0.1-beta0001 536 3/25/2019
2.0.0 47,005 3/19/2019
1.1.0 51,483 6/3/2018
1.0.0 9,270 6/1/2018
0.2.0-beta0047 375 8/5/2020
0.2.0-beta0045 363 8/4/2020
0.2.0-beta0036 374 7/31/2020
0.2.0-beta0035 379 7/31/2020
0.2.0-beta0034 426 6/22/2020