Ashfaq Hussain
Ashfaq Hussain

Reputation: 331

Mocking and verifying call to method containing an Expression<Func<T,bool>> parameter

I would like to mock this interface using Moq or RhinoMocks for the purpose of verifying that the correct expression is passed as a parameter (and willing to switch to any other open source mocking library that can support this):

Full source code:

public class Record
{
    public int RecordId { get; set; }
}

public interface IRepository
{
    void DeleteRecordsByFilter(Expression<Func<Record, bool>> filter);
}

public class MyClass
{
    private readonly IRepository _repo;

    public MyClass(IRepository repo)
    {
        _repo = repo;
    }

    public void DeleteRecords(int recordId)
    {
        _repo.DeleteRecordsByFilter(x => x.RecordId.Equals(recordId));
    }
}

[TestFixture]
public class MyFixture
{
    [Test]
    public void DeleteRecordsShouldCallDeleteRecordsByFilterOnRepo()
    {
        const int recordId = 10;

        var repo = new Mock<IRepository>();
        repo.Setup(method => method.DeleteRecordsByFilter(x => x.RecordId.Equals(recordId)));

        var sut = new MyClass(repo.Object);
        sut.DeleteRecords(recordId);

        repo.Verify(method => method.DeleteRecordsByFilter(x => x.RecordId.Equals(recordId)));
    }
}

When I execute the unit test it fails with the error:

Moq.MockException : Expected invocation on the mock at least once, but was never performed: method => method.DeleteRecordsByFilter(x => x.RecordId.Equals(10))

Configured setups: method => method.DeleteRecordsByFilter(x => x.RecordId.Equals(10)), Times.Never

Performed invocations: IRepository.DeleteRecordsByFilter(x => x.RecordId.Equals(value(MyClass+<>c__DisplayClass0).recordId))

Upvotes: 2

Views: 433

Answers (2)

StuartLC
StuartLC

Reputation: 107267

As per this post, comparisons of Expression can be quite fragile, as the default is by reference, and alternatives need to be sought. However, using the referenced method, viz by simple ToString() comparison of the Expression Body, you can verify the expression like so:

 var _mock = new Mock<IInterfaceToBeMocked>();

 // "Act" step -> invoke your CUT, which in turn calls the mocked interface dependency
 // (Obviously your CUT will do this, but just to prove the point ...)
 _mock.Object.DeleteRecordsByFilter(x => x.RecordId.Equals(10));

 // Back to the unit test 
 Expression<Func<Record, bool>> goodComparison = x => x.RecordId.Equals(10);
 Expression<Func<Record, bool>> badComparison = x => x.RecordId > 10 && x.RecordId < 12;
 _mock.Verify(m => m.DeleteRecordsByFilter(
    It.Is<Expression<Func<Record, bool>>>(ex => ex.Body.ToString() == goodComparison.Body.ToString())), 
    Times.Once);
 _mock.Verify(m => m.DeleteRecordsByFilter(
    It.Is<Expression<Func<Record, bool>>>(ex => ex.Body.ToString() == badComparison.Body.ToString())),
    Times.Never());

(i.e Moq retains a list of invoked parameters of type Expression, just like any other type of parameter which can be used in a Verify or Setup)

Edit

The above works for the trivial case where there are no external or closure variables in the expression and hence both expressions serialize equivalently. And the reference equality from @Oliver will work in the case where the CUT passes the same expression instance to the dependency. But in the general case, you would somehow need to determine whether 2 expressions are equivalent, e.g. to invoke both expressions with a set of known inputs and outputs (but this is more to do with the issue with the equivalence of Expressions / Functions / Lambdas - and not really a Moq issue).

Upvotes: 2

Olivier
Olivier

Reputation: 5688

If you're using Moq, and ar not willing to really check that the right argument is passed to your function, i guess that this is what you are looking for:

_mock.Setup(method => method.DeleteRecordsByFilter(It.IsAny<Expression<Func<Record,bool>>>());

(which is equilvalent to .IgnoreArguments() using RhinoMock)

The problem is that even if the two Expressions are the "same" (logically the same), they are not the same reference of object, and System.Linq.Expression does not implement .Equals() override. Thus, they are "different" for Moq.

That is why you get your exception.

If you wish to compare your expressions, you will have to setup a callback on your mock which will compare the two expressions (using an ExprssionVisitor for instance).

Edit

An an example, your test should work if implemented like this:

[Test]
public void DeleteRecordsShouldCallDeleteRecordsByFilterOnRepo()
{
    const int recordId = 10;

    var repo = new Mock<IRepository>();
    Expression<Func<Record,bool>> exp = x => x.RecordId.Equals(recordId);
    repo.Setup(method => method.DeleteRecordsByFilter(exp));

    var sut = new MyClass(repo.Object);
    sut.DeleteRecords(recordId);

    repo.Verify(method => method.DeleteRecordsByFilter(exp));
}

but its testing nothing :)

Upvotes: 1

Related Questions