Skip to content

Commit c30e9c5

Browse files
authored
Merge pull request #11 from adrianiftode/issue-10
Fix issue with the verification of the message args array
2 parents c6c0aec + fddabc0 commit c30e9c5

File tree

5 files changed

+52
-20
lines changed

5 files changed

+52
-20
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public void Verify_semantic_logging()
5252

5353
loggerMock.VerifyLog(logger => logger.LogInformation("Processed {@Position} in {Elapsed:000} ms.", new { Latitude = 25, Longitude = 134 }, 34));
5454
loggerMock.VerifyLog(logger => logger.LogInformation("Processed {@Position} in {Elapsed:000} ms.", It.IsAny<It.IsAnyType>(), It.IsAny<int>()));
55+
loggerMock.VerifyLog(logger => logger.LogInformation("Processed {@Position} in {Elapsed:000} ms.", It.Is<object[]>(arg => arg != null)));
5556

5657
// wildcard usages
5758
loggerMock.VerifyLog(logger => logger.LogInformation("Processed { Latitude = *, Longitude = * } in * ms."));

src/Moq.ILogger/VerifyLogExpression.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ internal class VerifyLogExpression
1212
public Expression ExceptionExpression { get; private set; }
1313
public Expression EventIdExpression { get; private set; }
1414
public Expression MessageArgsExpression { get; private set; }
15-
public bool HasExpectedMessageArgs => ((NewArrayExpression) MessageArgsExpression).Expressions.Count > 0;
15+
public bool HasExpectedMessageArgs => MessageArgsExpression is MethodCallExpression || (MessageArgsExpression as NewArrayExpression)?.Expressions.Count > 0;
1616

1717
public static VerifyLogExpression From(Expression expression) =>
1818
new VerifyLogExpression

src/Moq.ILogger/VerifyLogExtensions.cs

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ private static void Verify<T>(Mock<T> loggerMock, Expression<Action<ILogger>> ex
462462
Environment.NewLine +
463463
"Please open an issue at https://github.com/adrianiftode/Moq.ILogger/issues/new, provide the exception details and a sample code if possible." +
464464
Environment.NewLine +
465-
"Bellow will follow the unexpected exception details." +
465+
"Below will follow the unexpected exception details." +
466466
Environment.NewLine;
467467
throw new VerifyLogUnexpectedException(message, exception);
468468
}
@@ -773,37 +773,39 @@ private static string FormatLogValues(string format, object[] arguments)
773773

774774
private static bool MessageArgsMatch(Expression expectedMessageArgsExpression, object[] actualArgs)
775775
{
776-
// see this Test for reference https://github.com/moq/moq4/blob/61f420f3d44527ce652883ce857fc8b3bdabafca/tests/Moq.Tests/Matchers/ParamArrayMatcherFixture.cs#L19
776+
// Executes Moq specific code that evaluates if an object matches the description of an Expression.
777+
// In our case we need to see if the actualArgs passed into the formatter have the specified properties as described by expectedMessageArgsExpression:
778+
//
779+
// var (matcher, _) = MatcherFactory.CreateMatcher(expr, actualArgsParameter);
780+
// var isMatch = matcher.Matches(actualArgs, typeof(object[]));
777781

778-
// create Expression<> _ = x => x.Method(expectedMessageArgsExpression);
782+
// MatcherFactory and Match types are internal to Moq so this goal is achieved using reflection.
779783
//
784+
// see this Test for reference https://github.com/moq/moq4/blob/61f420f3d44527ce652883ce857fc8b3bdabafca/tests/Moq.Tests/Matchers/ParamArrayMatcherFixture.cs#L19
780785

786+
// create Expression<Action<IX>> _ = x => x.Method(args);
781787
var xParameter = Expression.Parameter(typeof(IX), "x");
782-
var instance = Expression.Constant(new X(), typeof(IX));
788+
var instance = Expression.Constant(null, typeof(IX));
783789
var methodCallExpression = Expression.Call(instance, typeof(IX).GetMethod("Method")!, expectedMessageArgsExpression);
784790
var methodCallLambda = Expression.Lambda(methodCallExpression, xParameter);
785-
var parameter = typeof(IX).GetMethod("Method")!.GetParameters().Single();
786791

787792
// Execute the following Moq code
788-
// var (matcher, _) = MatcherFactory.CreateMatcher(expr, parameter);
789-
// matcher.Matches(actualArgs, typeof(object[]))
790-
//
791-
793+
// var (matcher, _) = MatcherFactory.CreateMatcher(expr, argsParameter);
792794
var matcherFactoryType = typeof(Mock).Assembly.GetTypes().First(c => c.Name == "MatcherFactory");
793795
var createMatcherMethod =
794796
matcherFactoryType.GetMethod("CreateMatcher", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(Expression), typeof(ParameterInfo) }, null);
795797

796-
// result is typeof(Pair<IMatcher, Expression>); Pair is a type from Moq, having two fields Item1, Item2
798+
var argsParameter = typeof(IX).GetMethod("Method")!.GetParameters()[0];
799+
var matcherResult = createMatcherMethod!.Invoke(null, new object[] { ((MethodCallExpression)methodCallLambda.Body).Arguments[0], argsParameter });
800+
// matcherResult is typeof(Pair<IMatcher, Expression>); Pair is a type from Moq, having two fields Item1, Item2
797801
// we need Item1
798-
var matcherResult = createMatcherMethod!.Invoke(null, new object[] { ((MethodCallExpression)methodCallLambda.Body).Arguments.Single(), parameter });
799-
var resultType = matcherResult.GetType();
800-
var matcherField = resultType.GetField("Item1");
801-
var matcher = matcherField.GetValue(matcherResult);
802-
var matcherType = matcher.GetType();
802+
var matcher = matcherResult.GetType().GetField("Item1").GetValue(matcherResult);
803803

804-
// invoke matcher.Matches(actualArgs, typeof(object[]))
804+
// Remaining to execute the second line
805+
// var isMatch = matcher.Matches(actualArgs, typeof(object[]));
806+
var matcherType = matcher.GetType();
805807
var matchesMethod =
806-
matcherType.GetMethod("Matches", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(object[]), typeof(Type) }, null);
808+
matcherType.GetMethod("Matches", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new[] { typeof(object[]), typeof(Type) }, null);
807809
var isMatch = (bool)matchesMethod!.Invoke(matcher, new object[] { actualArgs, typeof(object[]) });
808810
return isMatch;
809811
}
@@ -826,5 +828,4 @@ private static string BuildExceptionMessage(MockException ex, Expression express
826828
}
827829

828830
internal interface IX { void Method(params object[] args); }
829-
internal class X : IX { public void Method(params object[] args) { throw new NotSupportedException("This method should never be called, only inspected."); } }
830831
}

tests/Moq.ILogger.Tests/Samples/SomeClassTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ public void Verify_semantic_logging()
9797

9898
loggerMock.VerifyLog(logger => logger.LogInformation("Processed {@Position} in {Elapsed:000} ms.", new { Latitude = 25, Longitude = 134 }, 34));
9999
loggerMock.VerifyLog(logger => logger.LogInformation("Processed {@Position} in {Elapsed:000} ms.", It.IsAny<It.IsAnyType>(), It.IsAny<int>()));
100+
loggerMock.VerifyLog(logger => logger.LogInformation("Processed {@Position} in {Elapsed:000} ms.", It.Is<object[]>(arg => arg != null)));
100101

101102
loggerMock.VerifyLog(logger => logger.LogInformation("Processed { Latitude = *, Longitude = * } in * ms."));
102103
loggerMock.VerifyLog(logger => logger.LogInformation("Processed * in * ms."));

tests/Moq.ILogger.Tests/VerifyLogExtensionsTests.cs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
using FluentAssertions;
12
using Microsoft.Extensions.Logging;
23
using System;
34
using Xunit;
4-
using FluentAssertions;
55

66
// ReSharper disable once CheckNamespace
77
namespace Moq.Tests
@@ -394,6 +394,35 @@ public void Verify_a_structured_message_with_not_matching_It_Is_parameters_it_th
394394
.WithMessage("*.LogInformation*");
395395
}
396396

397+
[Fact]
398+
public void Verify_when_args_array_is_matched_it_verifies()
399+
{
400+
var loggerMock = new Mock<ILogger>();
401+
var position = new { Latitude = 25, Longitude = 134 };
402+
var elapsedMs = 34;
403+
loggerMock.Object.LogInformation("Processed {@Position} in {Elapsed:000} ms.", position, elapsedMs);
404+
405+
Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation("Processed {@Position} in {Elapsed:000} ms.",
406+
It.IsAny<object[]>()));
407+
408+
act.Should().NotThrow();
409+
}
410+
411+
[Fact]
412+
public void Verify_when_args_array_is_not_matched_it_throws()
413+
{
414+
var loggerMock = new Mock<ILogger>();
415+
var position = new { Latitude = 25, Longitude = 134 };
416+
var elapsedMs = 34;
417+
loggerMock.Object.LogInformation("Processed {@Position} in {Elapsed:000} ms.", position, elapsedMs);
418+
419+
Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation("Processed {@Position} in {Elapsed:000} ms.",
420+
It.Is<object[]>(o => o == null)));
421+
422+
act.Should().ThrowExactly<VerifyLogException>()
423+
.WithMessage("*Expected invocation on the mock at least once*(o => o == null)*");
424+
}
425+
397426
[Fact]
398427
public void Verify_a_message_with_method_call_it_verifies()
399428
{

0 commit comments

Comments
 (0)