Moq verify method signature sees invocations but doesn't match on them?

Viewed 756

I have an interface like so:

public interface IStatisticsCollector : IDisposable
{
    Task Measure(string metricName, decimal value, IDictionary<string, string> tags = null);
}

I am injecting this IStatisticsCollector into my class and using it like this:

var stopwatch = new Stopwatch();
await dataCollector.Measure("rbk_init", stopwatch.ElapsedMilliseconds);
...
await dataCollector.Measure("rbk_compiled", stopwatch.ElapsedMilliseconds);
...

Setting up my unit test to verify that I am recording all the statistics points I want, I mock the IStatisticsCollector:

private readonly Mock<IStatisticsCollector> _statisticsCollector = new Mock<IStatisticsCollector>();
_statisticsCollector.Setup(x => x.Measure(It.IsAny<string>(), It.IsAny<long>(), It.IsAny<IDictionary<string, string>>())).Verifiable();

When I run my unit test, my verification fails on this line:

//assert
_statisticsCollector.Verify(
    x => x.Measure(It.IsAny<string>(), It.IsAny<long>(), It.IsAny<IDictionary<string, string>>()), Times.Exactly(5));

...with the following message:

Moq.MockException : 
Expected invocation on the mock exactly 5 times, but was 0 times: x => x.Measure(It.IsAny<string>(), (decimal)It.IsAny<long>(), It.IsAny<IDictionary<string, string>>())

Configured setups: 
IStatisticsCollector x => x.Measure(It.IsAny<string>(), (decimal)It.IsAny<long>(), It.IsAny<IDictionary<string, string>>())

Performed invocations: 
IStatisticsCollector.Measure("rbk_init", 31, null)
IStatisticsCollector.Measure("rbk_compiled", 35, null)
IStatisticsCollector.Measure("rbk_stored", 36, null)
IStatisticsCollector.Measure("rbk_db_updated", 352, null)
IStatisticsCollector.Measure("rbk_completed", 361, null)
   at Moq.Mock.VerifyCalls(Mock targetMock, InvocationShape expectation, LambdaExpression expression, Times times, String failMessage) in C:\projects\moq4\src\Moq\Mock.cs:line 378

... this was strange because it looks like it captured 5 matching invocations, but apparently didn't consider any of them to actually be matches. Now I postulated this might be related to the fact that the stopwatch's ElapsedMilliseconds was a long but the interface was expecting a decimal (with an implicit cast), so I changed the verify to look for It.IsAny<decimal>(), but that got me an unexpected result:

Moq.MockException : 
Expected invocation on the mock exactly 5 times, but was 1 times: x => x.Measure(It.IsAny<string>(), It.IsAny<decimal>(), It.IsAny<IDictionary<string, string>>())

Configured setups: 
IStatisticsCollector x => x.Measure(It.IsAny<string>(), It.IsAny<decimal>(), It.IsAny<IDictionary<string, string>>())

Performed invocations: 
IStatisticsCollector.Measure("rbk_init", 28, null)
   at Moq.Mock.VerifyCalls(Mock targetMock, InvocationShape expectation, LambdaExpression expression, Times times, String failMessage) in C:\projects\moq4\src\Moq\Mock.cs:line 378

It found one... and only one. There doesn't seem to be an exception thrown, so I don't know why there would only be one.

I also noted the null value on the optional argument, and tried to verify against a null value instead of an IDictionary<string, string>, but this was similarly fruitless.

Can someone explain this behavior? What do I need to do to fix my test?

1 Answers

THis line will always fail:

_statisticsCollector.Verify(
    x => x.Measure(It.IsAny<string>(), It.IsAny<long>(), It.IsAny<IDictionary<string, string>>()), Times.Exactly(5));

This happens because you are saying to the mock that it needs to receive a long in the second argument, BUT the interface says that in the second argument it will receive a decimal:

public interface IStatisticsCollector : IDisposable
{
    Task Measure(string metricName, decimal value, IDictionary<string, string> tags = null);
}

Since a decimal can't be a long, your verificatin will always fail.

Related