Skip to content

Commit

Permalink
Add VerifyLogger
Browse files Browse the repository at this point in the history
  • Loading branch information
cwinland committed Jun 27, 2024
1 parent d480d9a commit f556348
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 2 deletions.
128 changes: 126 additions & 2 deletions FastMoq.Core/Extensions/TestClassExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using FastMoq.Models;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using System.Collections;
using System.Linq.Expressions;
using System.Reflection;
Expand All @@ -12,7 +13,7 @@
namespace FastMoq.Extensions
{
/// <summary>
/// Class TestClassExtensions.
/// Helper Methods for testing.
/// </summary>
public static class TestClassExtensions
{
Expand Down Expand Up @@ -380,6 +381,129 @@ public static void SetFieldValue<TObject>(this TObject obj, string name, object?
public static void SetPropertyValue<TObject>(this TObject obj, string name, object? value) where TObject : class? =>
obj.GetProperty(name)?.SetValue(obj, value);

/// <summary>
/// Verifies the Mock ILogger was invoked.
/// </summary>
/// <param name="loggerMock">The logger mock.</param>
/// <param name="logLevel">The expected log level.</param>
/// <param name="message">The expected message.</param>
/// <param name="times">The expected number of invocations.</param>
public static void VerifyLogger(this Mock<ILogger> loggerMock, LogLevel logLevel, string message, int times = 1) => loggerMock.VerifyLogger(logLevel, message, null, null, times);

/// <summary>
/// Verifies the Mock ILogger of T was invoked.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="loggerMock">The logger mock.</param>
/// <param name="logLevel">The expected log level.</param>
/// <param name="message">The expected message.</param>
/// <param name="times">The expected number of invocations.</param>
public static void VerifyLogger<T>(this Mock<ILogger<T>> loggerMock, LogLevel logLevel, string message, int times = 1) => loggerMock.VerifyLogger(logLevel, message, null, null, times);

/// <summary>
/// Verifies the Mock ILogger was invoked.
/// </summary>
/// <param name="loggerMock">The logger mock.</param>
/// <param name="logLevel">The log level.</param>
/// <param name="message">The message.</param>
/// <param name="exception">The exception.</param>
/// <param name="eventId">The event identifier.</param>
/// <param name="times">The expected number of invocations.</param>
public static void VerifyLogger(this Mock<ILogger> loggerMock, LogLevel logLevel, string message, Exception? exception, int? eventId = null,
int times = 1) => loggerMock.VerifyLogger<Exception>(logLevel, message, exception, eventId, times);

/// <summary>
/// Verifies the Mock ILogger was invoked.
/// </summary>
/// <param name="loggerMock">The logger mock.</param>
/// <param name="logLevel">The log level.</param>
/// <param name="message">The message.</param>
/// <param name="exception">The exception.</param>
/// <param name="eventId">The event identifier.</param>
/// <param name="times">The expected number of invocations.</param>
public static void VerifyLogger<T>(this Mock<ILogger<T>> loggerMock, LogLevel logLevel, string message, Exception? exception, int? eventId = null,
int times = 1) => loggerMock.VerifyLogger<Exception, T>(logLevel, message, exception, eventId, times);

/// <summary>
/// Verifies the Mock ILogger was invoked.
/// </summary>
/// <typeparam name="TException">The type of the exception.</typeparam>
/// <param name="loggerMock">The logger mock.</param>
/// <param name="logLevel">The log level.</param>
/// <param name="message">The message.</param>
/// <param name="exception">The exception.</param>
/// <param name="eventId">The event identifier.</param>
/// <param name="times">The expected number of invocations.</param>
public static void VerifyLogger<TException>(this Mock<ILogger> loggerMock, LogLevel logLevel, string message, TException? exception, int? eventId = null, int times = 1)
where TException : Exception
=> loggerMock.Verify(TestLoggerExpression<TException, ILogger>(logLevel, message, exception, eventId), Times.Exactly(times));

/// <summary>
/// Verifies the Mock ILogger was invoked.
/// </summary>
/// <typeparam name="TException">The type of the exception.</typeparam>
/// <typeparam name="TLogger">The type of ILogger.</typeparam>
/// <param name="loggerMock">The logger mock.</param>
/// <param name="logLevel">The log level.</param>
/// <param name="message">The message.</param>
/// <param name="exception">The exception.</param>
/// <param name="eventId">The event identifier.</param>
/// <param name="times">The expected number of invocations.</param>
public static void VerifyLogger<TException, TLogger>(this Mock<ILogger<TLogger>> loggerMock, LogLevel logLevel, string message, TException? exception, int? eventId = null, int times = 1)
where TException : Exception
=> loggerMock.Verify(TestLoggerExpression<TException, ILogger<TLogger>>(logLevel, message, exception, eventId), Times.Exactly(times));

/// <summary>
/// Tests the logger expression2.
/// </summary>
/// <typeparam name="TException">The type of the exception.</typeparam>
/// <typeparam name="TLoggerType">The ILogger type.</typeparam>
/// <param name="logLevel">The log level.</param>
/// <param name="message">The message.</param>
/// <param name="exception">The exception.</param>
/// <param name="eventId">The event identifier.</param>
/// <returns>System.Linq.Expressions.Expression&lt;System.Action&lt;T&gt;&gt;.</returns>
internal static Expression<Action<TLoggerType>> TestLoggerExpression<TException, TLoggerType>(LogLevel logLevel, string message, TException? exception,
int? eventId) where TException : Exception where TLoggerType : ILogger =>
logger =>
logger.Log(
logLevel,
It.Is<EventId>(e => CheckEventId(e, eventId)),
It.Is<It.IsAnyType>((o, t) => CheckMessage(o.ToString() ?? string.Empty, t, message, t)),
It.Is<Exception>(e => CheckException(e, exception)),
It.IsAny<Func<It.IsAnyType, Exception?, string>>());

/// <summary>
/// Checks the expectedMessage.
/// </summary>
/// <param name="verifyMessage">The object.</param>
/// <param name="type">The type.</param>
/// <param name="expectedMessage">The expected expectedMessage.</param>
/// <param name="expectedType">The expected type.</param>
/// <returns>System.Boolean.</returns>
internal static bool CheckMessage(string verifyMessage, Type type, string expectedMessage, Type expectedType) =>
verifyMessage.Contains(expectedMessage, StringComparison.OrdinalIgnoreCase) &&
type.IsAssignableTo(expectedType);

/// <summary>
/// Checks the event identifier.
/// </summary>
/// <param name="verifyEventId">The event identifier.</param>
/// <param name="eventId">The expected event identifier.</param>
/// <returns>System.Boolean.</returns>
internal static bool CheckEventId(EventId verifyEventId, int? eventId) => eventId == null || verifyEventId == eventId;

/// <summary>
/// Checks the expectedException.
/// </summary>
/// <param name="verifyException">The expectedException.</param>
/// <param name="expectedException">The expected expectedException.</param>
/// <returns>System.Boolean.</returns>
internal static bool CheckException(Exception verifyException, Exception? expectedException) => expectedException == null ||
(verifyException.Message.Contains(
expectedException.Message, StringComparison.OrdinalIgnoreCase) &&
verifyException.GetType().IsAssignableTo(expectedException.GetType()));

/// <summary>
/// ForEach for <see cref="IEnumerable{T}" />.
/// </summary>
Expand Down Expand Up @@ -468,7 +592,7 @@ internal static MemberExpression GetMemberExpressionInternal(this Expression met
/// <param name="type">The type to try to create.</param>
/// <param name="constructors">The constructors to test with the specified type.</param>
/// <returns>List&lt;FastMoq.Models.ConstructorModel&gt;.</returns>
internal static List<ConstructorModel> GetTestedConstructors(this Mocker mocker, Type type, List<ConstructorModel> constructors)
internal static List<ConstructorModel> GetTestedConstructors(this Mocker mocker, Type type, List<ConstructorModel>? constructors)
{
constructors ??= new();
var validConstructors = new List<ConstructorModel>();
Expand Down
55 changes: 55 additions & 0 deletions FastMoq.Tests/MocksTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1028,6 +1028,61 @@ public void CallMethod_WithException()
Assert.Throws<ArgumentNullException>(() => Mocks.CallMethod<object?[]>(CallTestMethod, 4, null));
}

[Fact]
public void VerifyLogger_ShouldPass_WhenMatches()
{
var mLogger = new Mock<ILogger>();
mLogger.VerifyLogger(LogLevel.Information, "test", 0);

mLogger.Object.LogInformation("test");
mLogger.VerifyLogger(LogLevel.Information, "test");

mLogger.Object.LogInformation("test");
mLogger.VerifyLogger(LogLevel.Information, "test", 2);
mLogger.VerifyLogger(LogLevel.Information, "test", null, null, 2);

mLogger.Invocations.Clear();
mLogger.Object.LogError(1, new AmbiguousImplementationException("Test Exception"), "test message");
mLogger.VerifyLogger(LogLevel.Error, "test", new AmbiguousImplementationException("Test Exception"), 1);
mLogger.VerifyLogger<Exception>(LogLevel.Error, "test", new AmbiguousImplementationException("Test Exception"), 1);
}

[Fact]
public void VerifyLogger_ShouldPass_WhenMatchesILoggerSubtype()
{
var mLogger = new Mock<ILogger<NullLogger>>();
mLogger.VerifyLogger(LogLevel.Information, "test", 0);

mLogger.Object.LogInformation("test");
mLogger.VerifyLogger(LogLevel.Information, "test");

mLogger.Object.LogInformation("test");
mLogger.VerifyLogger(LogLevel.Information, "test", 2);
mLogger.VerifyLogger(LogLevel.Information, "test", null, null, 2);

mLogger.Invocations.Clear();
mLogger.Object.LogError(1, new AmbiguousImplementationException("Test Exception"), "test message");
mLogger.VerifyLogger(LogLevel.Error, "test", new AmbiguousImplementationException("Test Exception"), 1);
mLogger.VerifyLogger<Exception, NullLogger>(LogLevel.Error, "test", new AmbiguousImplementationException("Test Exception"), 1);
}

[Fact]
public void VerifyLogger_ShouldThrow_WhenNotMatches()
{
var mLogger = new Mock<ILogger>();
mLogger.VerifyLogger(LogLevel.Information, "test", 0);

mLogger.Object.LogInformation("test");
Assert.Throws<MockException>(() => mLogger.VerifyLogger(LogLevel.Information, "test2")); // Wrong Message.

mLogger.Object.LogInformation("test");
Assert.Throws<MockException>(() => mLogger.VerifyLogger(LogLevel.Information, "test")); // Wrong number of times.

mLogger.Invocations.Clear();
mLogger.Object.LogError(1, new AmbiguousImplementationException("Test Exception"), "test message");
Assert.Throws<MockException>(() => mLogger.VerifyLogger(LogLevel.Error, "test", new AmbiguousImplementationException("Test Exception"), 0)); // Wrong eventId.
}

private static void LogException(Exception ex, ILogger log, string customMessage = "", [CallerMemberName] string caller = "")
{
log.LogError("[{caller}] - {customMessage}{errorMessage}", caller, $"{customMessage} ", ex.Message);
Expand Down

0 comments on commit f556348

Please sign in to comment.