/MELT

MELT is a free, open source, testing library for the .NET Standard Microsoft Extensions Logging library. It is a solution to easily test logs.

Primary LanguageC#Apache License 2.0Apache-2.0

Testing Library for Microsoft Extensions Logging.

Build Status MELT Nuget MELT.AspNetCore Nuget MELT.Xunit Nuget

About MELT

MELT is a free, open-source, testing library for the .NET Standard Microsoft Extensions Logging library. It is a solution to easily test logs.

It is a repackaging with a sweetened API and some omissions of Microsoft.Extensions.Logging.Testing, a library used internally in ASP.NET Core for testing the logging, given that there is currently no plan to offer an official package for it.

It is licensed under Apache License 2.0. Most of the code is copyrighted by the .NET Foundation as mentioned in the files headers.

If you like this project please don't forget to star it on GitHub or let me know with a tweet.

You can find an explanation on the advantages of using this library and the importance of testing logs on the blog post "How to test logging when using Microsoft.Extensions.Logging".

Index

Quickstart

  • Install the NuGet package MELT

    <PackageReference Include="MELT" Version="0.4.0" />
  • Get a test logger factory

    var loggerFactory = MELTBuilder.CreateLoggerFactory();
  • Get a logger from the factory, as usual, to pass to your fixture.

    var logger = loggerFactory.CreateLogger<Sample>();

Assert log entries

The logger factory exposes a property LogEntries that enumerates all the logs captured. Each entry exposes all the relevant property for a log.

For example, to test with xUnit that a single log has been emitted and it had a specific message:

var log = Assert.Single(loggerFactory.LogEntries);
Assert.Equal("The answer is 42", log.Message);

Assert scopes

The logger factory exposes a property Scopes that enumerates all the scopes captured.

For example, to test with xUnit that a single scope has been emitted and it had a specific message:

var scope = Assert.Single(loggerFactory.Scopes);
Assert.Equal("I'm in the GET scope", scope.Message);

There is also a property Scope in each log entry to have the scope captured with that entry.

Assert message format

var log = Assert.Single(loggerFactory.LogEntries);
Assert.Equal("The answer is {number}", log.Format);

Easily test log or scope properties with xUnit

  • Install the NuGet package MELT.Xunit

    <PackageReference Include="MELT.Xunit" Version="0.4.0" />
  • Use the LogValuesAssert.Contains(...) helpers. For example, to test that a single log has been emitted and it had a property number with value 42:

    var log = Assert.Single(loggerFactory.LogEntries);
    LogValuesAssert.Contains("number", 42, log.Properties);

And much more

You can assert againt all the characteristic of a log entry: EventId, Exception, LoggerName, LogLevel, Message, Properties and Scope.

Full example

See SampleTest.

Quickstart for ASP.NET Core integration tests

  • Install the NuGet package MELT.AspNetCore

    <PackageReference Include="MELT.AspNetCore" Version="0.4.0" />
  • Create a test sink using MELTBuilder.CreateTestSink(...), where you can also customize the behaviour.

    For example to filter all log entries and scopes not generated by loggers consumed in the SampleWebApplication.* namespace (this filters the logger name so it assumes you are using ILogger<T> or following the default naming convention for your loggers.)

    ITestSink _sink = MELTBuilder.CreateTestSink(options => options.FilterByNamespace(nameof(SampleWebApplication)));

    You can also filter by logger name using FilterByTypeName<T>() or FilterByLoggerName(string name).

  • Use the AddTestLogger(...) extension method to add the test logger provider to the logging builder. This can be done where you are already configuring the web host builder.

    Configure the logger using WithWebHostBuilder on the factory.

    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Mvc.Testing;
    // ...
    var factory = factory.WithWebHostBuilder(builder => builder.UseTestLogging(_sink));

    Alternatively, you can configure the logger builder in the ConfigureWebHost implementation of your custom WebApplicationFactory<T>. If you chose so, the same sink will be used by all tests using the same factory. You can clear the sink in the test constructor with Clear() if you like to have a clean state before each test, as xUnit will not run tests consuming the same fixture in parallel.

    The logger will be automatically injected with Dependency Injection.

  • Alternatively, you can set it up in your custom WebApplicationFactory<TStartup>.

    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Mvc.Testing;
    
    public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup>
        where TStartup : class
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.UseTestLogging(options => options.FilterByNamespace(nameof(SampleWebApplication)));
            // ...
        }
    }

    You can then retrieve the sink to assert against using the extension method GetTestSink() on the factory.

    Please note that in this case, all tests sharing the same factory will get the same sink. You can reset it between tests with Clear() in the constructor of your xUnit tests. For example:

    public class LoggingTestWithInjectedFactory : IClassFixture<CustomWebApplicationFactory<Startup>>
    {
        private readonly CustomWebApplicationFactory<Startup> _factory;
    
        public LoggingTestWithInjectedFactory(CustomWebApplicationFactory<Startup> factory)
        {
            _factory = factory;
            // In this case the factory will be resused for all tests, so the sink will be shared as well.
            // We can clear the sink before each test execution, as xUnit will not run this tests in parallel.
            _factory.GetTestSink().Clear();
            // When running on 2.x, the server is not initialized until it is explicitly started or the first client is created.
            // So we need to use:
            // if (_factory.TryGetTestSink(out var testSink)) testSink!.Clear();
            // The exclamation mark is needed only when using Nullable Reference Types!
        }
    }

Assert log entries

The sink expose a property LogEntries that enumerates all the logs captured. Each entry exposes all the relevant property for a log.

For example, to test with xUnit that a single log has been emitted and it had a specific message:

var log = Assert.Single(sink.LogEntries);
Assert.Equal("The answer is 42", log.Message);

Assert scopes

The sink expose a property Scopes that enumerates all the scopes captured.

For example, to test with xUnit that a single scope has been emitted and it had a specific message:

var scope = Assert.Single(sink.Scopes);
Assert.Equal("I'm in the GET scope", scope.Message);

There is also a property Scope in each log entry to have the scope captured with that entry.

Assert message format

var log = Assert.Single(loggerFactory.LogEntries);
Assert.Equal("The answer is {number}", log.Format);

Easily test log or scope properties with xUnit

  • Install the NuGet package MELT.Xunit

    <PackageReference Include="MELT.Xunit" Version="0.4.0" />
  • Use the LogValuesAssert.Contains(...) helpers. For example, to test that a single log has been emitted and it had a property number with value 42:

    var log = Assert.Single(sink.LogEntries);
    LogValuesAssert.Contains("number", 42, log.Properties);

And much more

You can assert againt all the characteristic of a log entry: EventId, Exception, LoggerName, LogLevel, Message, Properties and Scope.

Full example

See LoggingTest or LoggingTestWithInjectedFactory.