Testing Library for Microsoft Extensions Logging.
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".
-
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>();
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);
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.
var log = Assert.Single(loggerFactory.LogEntries);
Assert.Equal("The answer is {number}", log.Format);
-
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 propertynumber
with value42
:var log = Assert.Single(loggerFactory.LogEntries); LogValuesAssert.Contains("number", 42, log.Properties);
You can assert againt all the characteristic of a log entry: EventId
, Exception
, LoggerName
, LogLevel
, Message
, Properties
and Scope
.
See SampleTest.
-
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 usingILogger<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>()
orFilterByLoggerName(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 customWebApplicationFactory<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 withClear()
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 yourxUnit
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! } }
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);
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.
var log = Assert.Single(loggerFactory.LogEntries);
Assert.Equal("The answer is {number}", log.Format);
-
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 propertynumber
with value42
:var log = Assert.Single(sink.LogEntries); LogValuesAssert.Contains("number", 42, log.Properties);
You can assert againt all the characteristic of a log entry: EventId
, Exception
, LoggerName
, LogLevel
, Message
, Properties
and Scope
.