PosInformatique.Logging.Assertions
1.0.3
Prefix Reserved
See the version list below for details.
dotnet add package PosInformatique.Logging.Assertions --version 1.0.3
NuGet\Install-Package PosInformatique.Logging.Assertions -Version 1.0.3
<PackageReference Include="PosInformatique.Logging.Assertions" Version="1.0.3" />
paket add PosInformatique.Logging.Assertions --version 1.0.3
#r "nuget: PosInformatique.Logging.Assertions, 1.0.3"
// Install PosInformatique.Logging.Assertions as a Cake Addin #addin nuget:?package=PosInformatique.Logging.Assertions&version=1.0.3 // Install PosInformatique.Logging.Assertions as a Cake Tool #tool nuget:?package=PosInformatique.Logging.Assertions&version=1.0.3
PosInformatique.Logging.Assertions
PosInformatique.Logging.Assertions is a library to mock and assert easily the logs generated by the ILogger interface.
Installing from NuGet
The PosInformatique.Logging.Assertions library is available directly on the official website.
To download and install the library to your Visual Studio unit test projects use the following NuGet command line
Install-Package PosInformatique.Logging.Assertions
How it is work?
Imagine that you have the following class which contains various log:
public class CustomerManager
{
private readonly IEmailProvider emailProvider;
private readonly ILogger<CustomerManager> logger;
public CustomerManager(IEmailProvider emailProvider, ILogger<CustomerManager> logger)
{
this.emailProvider = emailProvider;
this.logger = logger;
}
public async Task SendEmailAsync(int id, string name)
{
this.logger.LogInformation($"Starting to send an email to the customer '{name}'");
using (this.logger.BeginScope(new { Id = id }))
{
try
{
this.logger.LogDebug($"Call the SendAsync() method");
await this.emailProvider.SendAsync(name);
this.logger.LogDebug($"SendAsync() method has been called.");
this.logger.LogInformation($"Email provider has been called.");
}
catch (Exception exception)
{
this.logger.LogError(exception, "Unable to send the email !");
}
}
}
}
public interface IEmailProvider
{
Task SendAsync(string name);
}
As a good developer (like me), who always do unit tests with 100% of code coverage, you have to write the unit tests
to test the SendEmailAsync()
method and mock the IEmailProvider
and ILogger<T>
interfaces with your favorite mocking library.
(Moq for me...).
Some developers consider that log information should not be test with unit tests... 😆 😆
ILogger
interface and his methods calls should be test as any normal code and specially the scope to be sure we inject the right data. Most often, developpers discover that their own logs don't log things correctly in production environments... 😆 😆
So the unit test to write for the previous example should look like something like that:
[Fact]
public async Task SendEmailAsync()
{
// Arrange
var emailProvider = new Mock<IEmailProvider>(MockBehavior.Strict);
emailProvider.Setup(ep => ep.SendAsync("Gilles TOURREAU"))
.Returns(Task.CompletedTask);
var logger = new Mock<ILogger<CustomerManager>>(MockBehavior.Strict);
logger.Setup(l => l.Log(LogLevel.Information, "Starting to send an email to the customer 'Gilles TOURREAU'", ..., ..., ... )) // WTF???
...
logger.Setup(l => l.BeginScope<...>(...)) // WTF???
var manager = new CustomerManager(emailProvider.Object, logger.Object);
// Act
await manager.SendEmailAsync(1234, "Gilles TOURREAU");
// Assert
emailProvider.VerifyAll();
logger.VerifyAll();
}
As your can see, it is very hard to mock the Log() method of the ILogger interface which have the following signature:
void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
And also to check the scope usage with the ILogger interface it can be hard !
The PosInformatique.Logging.Assertions library allows the developpers to mock the ILogger easily and setup the sequence of the expected logs using a fluent style code. For the previous example, this is how the unit test look like for the previous example.
[Fact]
public async Task SendEmailAsync()
{
// Arrange
var emailProvider = new Mock<IEmailProvider>(MockBehavior.Strict);
emailProvider.Setup(ep => ep.SendAsync("Gilles TOURREAU"))
.Returns(Task.CompletedTask);
var logger = new LoggerMock<CustomerManager>();
logger.SetupSequence()
.LogInformation("Starting to send an email to the customer 'Gilles TOURREAU'")
.BeginScope(new { Id = 1234 })
.LogDebug("Call the SendAsync() method")
.LogDebug("SendAsync() method has been called.")
.LogInformation("Email provider has been called.")
.EndScope();
var manager = new CustomerManager(emailProvider.Object, logger.Object);
// Act
await manager.SendEmailAsync(1234, "Gilles TOURREAU");
// Assert
emailProvider.VerifyAll();
logger.VerifyLogs();
}
😍 😍 Sexy isn't it??? The unit test is more easy to read and write!
Do not forget to call the
VerifyLogs()
at the end of your unit test like aVerifyAll()
call with the Moq library. TheVerifyLogs()
will check that all methods setup (Arrange) are called by the code under test (Act).
Do not hesitate to use the indentation to make the fluent code more readable specially when using nested scopes.
For example to check nested log scopes write the following code with the following indented code:
var logger = new LoggerMock<CustomerManager>();
logger.SetupSequence()
.LogInformation("Starting to send an email to the customer 'Gilles TOURREAU'")
.BeginScope(new { Id = 1234 })
.BeginScope(new { Name = "Gilles" })
.LogError("Error in the scope 1234 + Gilles")
.EndScope()
.LogInformation("Log between the 2 nested scopes.")
.BeginScope(new { Name = "Aiza" })
.LogError("Error in the scope 1234 + Aiza")
.EndScope()
.EndScope();
Test the error logs with an exception.
To test an Exception
with specified in the LogError()
method of the ILogger
interface use the
WithException()
method an set the instance expected:
[Fact]
public async Task SendEmailAsync_WithException()
{
// Arrange
var theException = new FormatException("An exception");
var emailProvider = new Mock<IEmailProvider>(MockBehavior.Strict);
emailProvider.Setup(ep => ep.SendAsync("Gilles TOURREAU"))
.ThrowsAsync(theException);
var logger = new LoggerMock<CustomerManager>();
logger.SetupSequence()
.LogInformation("Starting to send an email to the customer 'Gilles TOURREAU'")
.BeginScope(new { Id = 1234 })
.LogDebug("Call the SendAsync() method")
.LogError("Unable to send the email !")
.WithException(theException)
.EndScope();
var manager = new CustomerManager(emailProvider.Object, logger.Object);
// Act
await manager.Invoking(m => m.SendEmailAsync(1234, "Gilles TOURREAU"))
.Should().ThrowExactlyAsync<FormatException>();
// Assert
emailProvider.VerifyAll();
logger.VerifyLogs();
}
In the case the exception is throw by the code (It is mean the exception is not produced by the unit test during the Arrange phase), use the version with a delegate to check the content of the Exception:
var logger = new LoggerMock<CustomerManager>();
logger.SetupSequence()
.LogInformation("Starting to send an email to the customer 'Gilles TOURREAU'")
.BeginScope(new { Id = 1234 })
.LogDebug("Call the SendAsync() method")
.LogError("Unable to send the email !")
.WithException(e =>
{
e.Message.Should().Be("An exception");
e.InnerException.Should().BeNull();
})
.EndScope();
Assertion fail messages
The PosInformatique.Logging.Assertions library try to make the assert fail messages the most easy to understand for the developers:
Library dependencies
The PosInformatique.Logging.Assertions target the .NET Standard 2.0 and the version 2.0.0 of the Microsoft.Extensions.Logging.Abstractions NuGet package. So this library can be used with different .NET architecture projects (.NET Framework, .NET Core, Xamarin,...) and also with old versions of the Microsoft.Extensions.Logging.Abstractions NuGet package.
The PosInformatique.Logging.Assertions library depends of the FluentAssertions library for internal assertions which is more pretty to read in the exceptions message. It is use the version 6.0.0 and of course it is compatible with the earlier version of it.
Also, the PosInformatique.Logging.Assertions library use the internal FluentAssertions unit test provider engine detection to throw an exception (when an assertion is false) depending of the engine used to execute the unit test. For example,
XunitException
if the unit test engine used isXunit
.
Product | Versions 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. |
-
.NETStandard 2.0
- FluentAssertions (>= 6.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 2.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
1.5.1 | 728 | 3/15/2024 |
1.5.1-rc.1 | 60 | 3/15/2024 |
1.5.0 | 232 | 2/26/2024 |
1.5.0-rc.2 | 73 | 2/22/2024 |
1.5.0-rc.1 | 78 | 2/19/2024 |
1.4.0 | 651 | 11/30/2023 |
1.3.0 | 203 | 10/24/2023 |
1.2.0 | 193 | 10/19/2023 |
1.1.0 | 156 | 10/16/2023 |
1.0.3 | 139 | 10/16/2023 |
1.0.2 | 179 | 10/13/2023 |
1.0.1 | 155 | 10/11/2023 |
1.0.0 | 152 | 10/11/2023 |
1.0.3
- Use FluentAssertions 6.0.0 library.
1.0.2
- The library target the .NET Standard 2.0 instead of .NET 6.0.
1.0.1
- Various fixes for the NuGet package description.
1.0.0
- Initial version