Skip to content

Commit 1efc9e4

Browse files
v1.4.0 (#16)
* Add the non generic ILogger mock support (fixes #14). * Do not throw an exception in the Dispose() block of BeginScope() if an exception is ongoing (#13).
1 parent a38b853 commit 1efc9e4

File tree

9 files changed

+284
-73
lines changed

9 files changed

+284
-73
lines changed

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,26 @@ try to make the assert fail messages the most easy to understand for the develop
377377
![Assertion Failed Too Many Calls](https://raw.githubusercontent.com/PosInformatique/PosInformatique.Logging.Assertions/main/docs/AssertionFailedTooManyCalls.png)
378378
![Assertion Missing Logs](https://raw.githubusercontent.com/PosInformatique/PosInformatique.Logging.Assertions/main/docs/AssertionMissingLogs.png)
379379

380+
## Non generic support of the ILogger interface
381+
The [PosInformatique.Logging.Assertions](https://www.nuget.org/packages/PosInformatique.Logging.Assertions/) library
382+
allows to mock the `ILogger<T>` and `ILogger` interfaces.
383+
384+
To mock a `ILogger<T>` implementation use the following code:
385+
```csharp
386+
var logger = new LoggerMock<CustomerManager>();
387+
logger.SetupSequence()
388+
.LogInformation("...");
389+
```
390+
391+
To mock a `ILogger` implementation use the following code:
392+
```csharp
393+
var logger = new LoggerMock();
394+
logger.SetupSequence()
395+
.LogInformation("...");
396+
```
397+
398+
Both usage offers the same fluent assertion methods.
399+
380400
## Library dependencies
381401
- The [PosInformatique.Logging.Assertions](https://www.nuget.org/packages/PosInformatique.Logging.Assertions/) target the .NET Standard 2.0
382402
and the version 2.0.0 of the [Microsoft.Extensions.Logging.Abstractions](https://www.nuget.org/packages/Microsoft.Extensions.Logging.Abstractions) NuGet package. So this library can be used with

build/azure-pipelines-release.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ parameters:
22
- name: VersionPrefix
33
displayName: The version of the library
44
type: string
5-
default: 1.0.0
5+
default: 1.4.0
66
- name: VersionSuffix
77
displayName: The version suffix of the library (rc.1). Use a space ' ' if no suffix.
88
type: string
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//-----------------------------------------------------------------------
2+
// <copyright file="ExceptionHelper.cs" company="P.O.S Informatique">
3+
// Copyright (c) P.O.S Informatique. All rights reserved.
4+
// </copyright>
5+
//-----------------------------------------------------------------------
6+
7+
namespace PosInformatique.Logging.Assertions
8+
{
9+
using System.Reflection;
10+
using System.Runtime.InteropServices;
11+
12+
internal static class ExceptionHelper
13+
{
14+
private static readonly MethodInfo GetExceptionPointersMethod = typeof(Marshal).GetMethod("GetExceptionPointers");
15+
16+
public static bool IsExceptionOnGoing()
17+
{
18+
var ptr = (IntPtr)GetExceptionPointersMethod.Invoke(null, null);
19+
20+
if (ptr == IntPtr.Zero)
21+
{
22+
return false;
23+
}
24+
25+
return true;
26+
}
27+
}
28+
}

src/Logging.Assertions/LoggerMock.cs

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66

77
namespace PosInformatique.Logging.Assertions
88
{
9-
using System.Diagnostics.CodeAnalysis;
10-
using System.Net.NetworkInformation;
119
using FluentAssertions;
1210
using FluentAssertions.Common;
1311
using Microsoft.Extensions.Logging;
@@ -26,26 +24,25 @@ namespace PosInformatique.Logging.Assertions
2624
/// calls to the <see cref="ILogger"/> methods.</item>
2725
/// </list>
2826
/// </summary>
29-
/// <typeparam name="TCategoryName">The category name of the <see cref="ILogger{TCategoryName}"/> to create.</typeparam>
30-
public sealed class LoggerMock<TCategoryName>
27+
public class LoggerMock
3128
{
3229
private readonly IList<ExpectedLogAction> expectedLogActions;
3330

3431
private int expectedLogActionsIndex;
3532

3633
/// <summary>
37-
/// Initializes a new instance of the <see cref="LoggerMock{TCategoryName}"/> class.
34+
/// Initializes a new instance of the <see cref="LoggerMock"/> class.
3835
/// </summary>
3936
public LoggerMock()
4037
{
4138
this.expectedLogActions = new List<ExpectedLogAction>();
42-
this.Object = new LoggerRecorder(this);
39+
this.Object = this.CreateRecorder<object>();
4340
}
4441

4542
/// <summary>
46-
/// Gets the mocked instance of the <see cref="ILogger{TCategoryName}"/>.
43+
/// Gets the mocked instance of the <see cref="ILogger"/>.
4744
/// </summary>
48-
public ILogger<TCategoryName> Object { get; }
45+
public ILogger Object { get; }
4946

5047
/// <summary>
5148
/// Entry method which allows to setup the sequence of the expected <see cref="ILogger"/> method calls.
@@ -70,11 +67,21 @@ public void VerifyLogs()
7067
}
7168
}
7269

70+
/// <summary>
71+
/// Creates an instance of <see cref="LoggerRecorder{TCategoryName}"/> to record <see cref="ILogger"/> trace.
72+
/// </summary>
73+
/// <typeparam name="TCategoryName">Type of the category name to use for <see cref="ILogger{TCategoryName}"/>.</typeparam>
74+
/// <returns>An instance of <see cref="LoggerRecorder{TCategoryName}"/> to record <see cref="ILogger"/> trace.</returns>
75+
private protected virtual ILogger CreateRecorder<TCategoryName>()
76+
{
77+
return new LoggerRecorder<TCategoryName>(this);
78+
}
79+
7380
private sealed class LoggerMockSetupSequence : ILoggerMockSetupSequence
7481
{
75-
private readonly LoggerMock<TCategoryName> mock;
82+
private readonly LoggerMock mock;
7683

77-
public LoggerMockSetupSequence(LoggerMock<TCategoryName> mock)
84+
public LoggerMockSetupSequence(LoggerMock mock)
7885
{
7986
this.mock = mock;
8087
}
@@ -116,11 +123,11 @@ public ILoggerMockSetupSequenceError LogError(string message)
116123
}
117124
}
118125

119-
private sealed class LoggerRecorder : ILogger<TCategoryName>
126+
private sealed class LoggerRecorder<TCategoryName> : ILogger, ILogger<TCategoryName>
120127
{
121-
private readonly LoggerMock<TCategoryName> mock;
128+
private readonly LoggerMock mock;
122129

123-
public LoggerRecorder(LoggerMock<TCategoryName> mock)
130+
public LoggerRecorder(LoggerMock mock)
124131
{
125132
this.mock = mock;
126133
}
@@ -170,18 +177,22 @@ private TLogAction GetCurrentExpectedLogAction<TLogAction>(string methodCall)
170177

171178
private sealed class LogScopeDisposable : IDisposable
172179
{
173-
private readonly LoggerRecorder recorder;
180+
private readonly LoggerRecorder<TCategoryName> recorder;
174181

175-
public LogScopeDisposable(LoggerRecorder recorder)
182+
public LogScopeDisposable(LoggerRecorder<TCategoryName> recorder)
176183
{
177184
this.recorder = recorder;
178185
}
179186

180187
public void Dispose()
181188
{
182-
this.recorder.GetCurrentExpectedLogAction<ExpectedLogEndScope>("Dispose()");
189+
// If an exception is ongoing, we don't thrown an exception an let the original exception propagated.
190+
if (!ExceptionHelper.IsExceptionOnGoing())
191+
{
192+
this.recorder.GetCurrentExpectedLogAction<ExpectedLogEndScope>("Dispose()");
183193

184-
this.recorder.mock.expectedLogActionsIndex++;
194+
this.recorder.mock.expectedLogActionsIndex++;
195+
}
185196
}
186197
}
187198
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//-----------------------------------------------------------------------
2+
// <copyright file="LoggerMock{TCategoryName}.cs" company="P.O.S Informatique">
3+
// Copyright (c) P.O.S Informatique. All rights reserved.
4+
// </copyright>
5+
//-----------------------------------------------------------------------
6+
7+
namespace PosInformatique.Logging.Assertions
8+
{
9+
using Microsoft.Extensions.Logging;
10+
11+
/// <summary>
12+
/// Allows to mock and retrieve an instance of <see cref="ILogger{TCategoryName}"/>. To mock a <see cref="ILogger{TCategoryName}"/>:
13+
/// <list type="bullet">
14+
/// <item>Creates an instance of the <see cref="LoggerMock{TCategoryName}"/>.</item>
15+
/// <item>
16+
/// Setup the expected the calls to the
17+
/// <see cref="ILogger.Log{TState}(LogLevel, EventId, TState, Exception?, Func{TState, Exception?, string})"/> and
18+
/// <see cref="ILogger.BeginScope{TState}(TState)"/> methods.
19+
/// </item>
20+
/// <item>Gets the mocked instance of the <see cref="ILogger{TCategoryName}"/> using the <see cref="object"/> property.</item>
21+
/// <item>Do not forget to call the <see cref="LoggerMock.VerifyLogs"/> method at the end of the unit test to check that there is no missing
22+
/// calls to the <see cref="ILogger"/> methods.</item>
23+
/// </list>
24+
/// </summary>
25+
/// <typeparam name="TCategoryName">The category name of the <see cref="ILogger{TCategoryName}"/> to create.</typeparam>
26+
public class LoggerMock<TCategoryName> : LoggerMock
27+
{
28+
/// <summary>
29+
/// Initializes a new instance of the <see cref="LoggerMock{TCategoryName}"/> class.
30+
/// </summary>
31+
public LoggerMock()
32+
{
33+
}
34+
35+
/// <summary>
36+
/// Gets the mocked instance of the <see cref="ILogger{TCategoryName}"/>.
37+
/// </summary>
38+
public new ILogger<TCategoryName> Object => (ILogger<TCategoryName>)base.Object;
39+
40+
/// <inheritdoc/>
41+
private protected override ILogger CreateRecorder<TIgnored>()
42+
{
43+
return base.CreateRecorder<TCategoryName>();
44+
}
45+
}
46+
}

src/Logging.Assertions/Logging.Assertions.csproj

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,28 +10,32 @@
1010
<PackageProjectUrl>https://github.com/PosInformatique/PosInformatique.Logging.Assertions</PackageProjectUrl>
1111
<PackageReadmeFile>README.md</PackageReadmeFile>
1212
<PackageReleaseNotes>
13-
1.3.0
14-
- Fix an assertion message with the `WithArguments()` method.
15-
- Add the support of the `WithException()` followed by the `WithArguments()` method. (fixes #11).
16-
17-
1.2.0
18-
- Improve the BeginScope() to use a delegate to assert complex state objects.
19-
- Add a BeginScopeAsDictionary() method to check the state as dictionary string/object (useful for Application Insights logs).
20-
21-
1.1.0
22-
- Add the support to assert the log message template and the parameters.
23-
24-
1.0.3
25-
- Use FluentAssertions 6.0.0 library.
26-
27-
1.0.2
28-
- The library target the .NET Standard 2.0 instead of .NET 6.0.
29-
30-
1.0.1
31-
- Various fixes for the NuGet package description.
32-
33-
1.0.0
34-
- Initial version
13+
1.4.0
14+
- Add the ILogger non generic support.
15+
- Do not hide the initial exception occurred in a BeginScope() block.
16+
17+
1.3.0
18+
- Fix an assertion message with the `WithArguments()` method.
19+
- Add the support of the `WithException()` followed by the `WithArguments()` method. (fixes #11).
20+
21+
1.2.0
22+
- Improve the BeginScope() to use a delegate to assert complex state objects.
23+
- Add a BeginScopeAsDictionary() method to check the state as dictionary string/object (useful for Application Insights logs).
24+
25+
1.1.0
26+
- Add the support to assert the log message template and the parameters.
27+
28+
1.0.3
29+
- Use FluentAssertions 6.0.0 library.
30+
31+
1.0.2
32+
- The library target the .NET Standard 2.0 instead of .NET 6.0.
33+
34+
1.0.1
35+
- Various fixes for the NuGet package description.
36+
37+
1.0.0
38+
- Initial version
3539
</PackageReleaseNotes>
3640
<PackageTags>logger log fluent unittest assert assertions logging mock</PackageTags>
3741
</PropertyGroup>

stylecop.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
"settings": {
33
"documentationRules": {
44
"companyName": "P.O.S Informatique",
5-
"copyrightText": "Copyright (c) {companyName}. All rights reserved."
5+
"copyrightText": "Copyright (c) {companyName}. All rights reserved.",
6+
"documentInternalElements": false
67
}
78
}
89
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//-----------------------------------------------------------------------
2+
// <copyright file="ExceptionHelperTest.cs" company="P.O.S Informatique">
3+
// Copyright (c) P.O.S Informatique. All rights reserved.
4+
// </copyright>
5+
//-----------------------------------------------------------------------
6+
7+
namespace PosInformatique.Logging.Assertions.Tests
8+
{
9+
using FluentAssertions;
10+
using Xunit;
11+
12+
public class ExceptionHelperTest
13+
{
14+
[Fact]
15+
public void IsExceptionOnGoing()
16+
{
17+
ExceptionHelper.IsExceptionOnGoing().Should().BeFalse();
18+
19+
try
20+
{
21+
ExceptionHelper.IsExceptionOnGoing().Should().BeFalse();
22+
23+
throw new Exception("Oups...");
24+
}
25+
catch
26+
{
27+
ExceptionHelper.IsExceptionOnGoing().Should().BeTrue();
28+
}
29+
30+
ExceptionHelper.IsExceptionOnGoing().Should().BeFalse();
31+
}
32+
}
33+
}

0 commit comments

Comments
 (0)