Reputation: 331
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
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
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