ErikTJ ErikTJ - 3 months ago 16
C# Question

Moq It.Is<> not matching

This code:

hub.MockedUserRepository.Setup(r => r.Update(It.IsAny<ControllUser>()))
.Callback((ControllUser usr) => Console.WriteLine("NULL = " + (usr.Zombies[0].ConnectionId == null)))
.Verifiable();


Will print


NULL = True


So i am thinking using this matching will catch it:

var zombieDisconnectParameterMatcher = It.Is<ControllUser>(x => x.Zombies[0].ConnectionId == null);
hub.MockedUserRepository.Setup(r => r.Update(zombieDisconnectParameterMatcher))
.Callback((ControllUser usr) => Console.WriteLine("NULL = " + (usr.Zombies[0].ConnectionId == null)))
.Verifiable();


But it does not.

Why?

Answer

By looking at the source code of It, it has to do with expression trees. I like the question; they can be quite puzzling. If you would take a look at the following method definitions:

public static TValue It.Is<TValue>(Expression<Func<TValue, bool>> match)
{
        return Match<TValue>.Create(
                value => match.Compile().Invoke(value),
                () => It.Is<TValue>(match));
}

public static T Match.Create<T>(Predicate<T> condition, Expression<Func<T>> renderExpression)
{
        // ...
        return default(T);
}

If you would execute the following line:

var zombieDisconnectParameterMatcher = It.Is<ControllUser>(x => x.Zombies[0].ConnectionId == null);

Then It.Is<ControllUser>() will try to call a method called Match.Create<ControllUser>(), which returns the default of ControllUser. I assume ControllUser is a class and therefore zombieDisconnectParameterMatcher will be null. You should be able to see this with the debugger. So what actually you're calling is:

hub.MockedUserRepository.Setup(r => r.Update(null))
    .Callback((ControllUser usr) => Console.WriteLine("NULL = " + (usr.Zombies[0].ConnectionId == null)))
    .Verifiable();

When executing the Update method with a non-null ControllUser, the callback will not trigger. It doesn't match the criteria since it's not null. Also you would see the verification fail.

To resolve this issue, either inline the zombieDisconnectParameterMatcher variable, or make it an expression typed variable (eg. Expression<Func<...>>). So that it stays an expression and will not be executed, but will be evaluated by the mock instead.