Nick
Nick

Reputation: 6588

Why does using an non-inlined It.IsAny<T> in Moq not work correctly?

Assigning It.IsAny<T>() to a variable for use in a Setup on a mocked object does not work as expected: the test shown below fails.

However, if I inline the anyString variable the test passes. What's going on here?

public class MyService
{
    private readonly IDependency _dependency;

    public MyService(IDependency dependency)
    {
        _dependency = dependency;
    }

    public string UseTheDependency(string input)
    {
        return _dependency.GetValue(input);
    }
}

public interface IDependency
{
    string GetValue(string input);
}

public class Tests
{
    [Test]
    public void TestTheTestClass()
    {
        var mockDependency = new Mock<IDependency>();
        var anyString = It.IsAny<string>();
        mockDependency.Setup(x => x.GetValue(anyString)).Returns("expected value");
        var service = new MyService(mockDependency.Object);
        var result = service.UseTheDependency("something random");
        Assert.That(result, Is.EqualTo("expected value"));
    }
}

Upvotes: 2

Views: 153

Answers (1)

Thomas Levesque
Thomas Levesque

Reputation: 292435

This is because the Setup method takes a Linq Expression (Expression<Func<IDependency, string>>) as a parameter, not a delegate (Func<IDependency, string>). It allows Moq to inspect the abstract syntax tree to know what call is being configured. If you use a variable declared outside the expression, Moq doesn't know that you used It.IsAny, it just sees a null (technically, it sees an access to the field anyString of the object that encapsulates the captured local variables, and that field just contains null).


EDIT: I wrote this answer a long time ago, and I now realize it was partly incorrect. I've been a maintainer of FakeItEasy (another mocking library) for many years, and it uses an approch similar to Moq's, so now I have a better understanding of it...

It's not true that It.IsAny should never executed; it is executed, and produces an argument constraint. But that constraint is only captured when evaluating the call expression in Setup, when Moq has the context for that constraint. If you just call It.IsAny outside the call to Setup, when there is no context for it, the produced constraint isn't captured, and is just discarded.

Upvotes: 3

Related Questions