32

I am Mocking my repository interface and am not sure how to set up a method that takes an expression and returns an object? I am using Moq and NUnit.

Interface:

public interface IReadOnlyRepository : IDisposable
{
    IQueryable<T> All<T>() where T : class;
    T Single<T>(Expression<Func<T, bool>> expression) where T : class;
}

Test with IQueryable is already set up, but don't know how to set up the T Single:

private Moq.Mock<IReadOnlyRepository> _mockRepos;
private AdminController _controller;
[SetUp]
public void SetUp()
{
    var allPages = new List<Page>();
    for (var i = 0; i < 10; i++)
    {
        allPages.Add(new Page { Id = i, Title = "Page Title " + i, Slug = "Page-Title-" + i, Content = "Page " + i + " on page content." });
    }
    _mockRepos = new Moq.Mock<IReadOnlyRepository>();
    _mockRepos.Setup(x => x.All<Page>()).Returns(allPages.AsQueryable());
    //Not sure what to do here???
    _mockRepos.Setup(x => x.Single<Page>()
    //----
    _controller = new AdminController(_mockRepos.Object);
}
Boann
  • 48,794
  • 16
  • 117
  • 146
Paul
  • 12,392
  • 4
  • 48
  • 58

4 Answers4

43

You can set it up like this:

_mockRepos.Setup(x => x.Single<Page>(It.IsAny<Expression<Func<Page, bool>>>()))//.Returns etc...;

However you are coming up against one of Moq's shortcomings. You would want to put an actual expression there instead of using It.IsAny, but Moq doesn't support setting up methods that take expressions with specific expressions (it's a difficult feature to implement). The difficulty comes from having to figure out whether two expressions are equivalent.

So in your test you can pass in any Expression<Func<Page,bool>> and it will pass back whatever you have setup the mock to return. The value of the test is a little diluted.

Restore the Data Dumps
  • 38,967
  • 12
  • 96
  • 122
  • 1
    Thanks for the reply. I am getting the error with the code above: Error 2 Argument '1': cannot convert from 'method group' to 'System.Linq.Expressions.Expression> – Paul May 02 '10 at 00:03
  • @Paul: Sorry, I dropped the `()`. Try with the latest version and it should work. – Restore the Data Dumps May 02 '10 at 00:06
  • Thanks for the reply, that worked, not ideal like you mentioned, but works! Thanks again. – Paul May 02 '10 at 00:11
  • Definitely not ideal, especially if you need to call Single twice with two different expressions, and return something different depending on the expressions/situations (which is the problem I am having currently). – jamiebarrow May 07 '12 at 11:24
  • Thanks for this, have spent a few unhappy hours trying to work out why my specific expression test wasn't working. – DaveRead Oct 12 '12 at 15:27
  • Thanks for this - I didn't know this about MOQ. I don't suppose if anyone knows if this feature might be added in future? – John Reilly Jan 15 '13 at 16:24
  • See my answer below, there is a pretty easy way to do this. – keithwill Sep 28 '17 at 14:34
8

Have the .Returns call return the result of the expression against your allPages variable.

_mockRepos.Setup(x => x.Single<Page>(It.IsAny<Expression<Func<Page, bool>>>()))
    .Returns( (Expression<Func<Page, bool>> predicate) => allPages.Where(predicate) );
keithwill
  • 1,964
  • 1
  • 17
  • 26
4

I have found that It.Is<T> should be used in place of It.IsAny<T> for more accurate results.

Page expectedPage = new Page {Id = 12, Title = "Some Title"};
_mockRepos.Setup(x => x.Single<Page>(It.Is<Expression<Func<Page, bool>>>(u => u.Compile().Invoke(expectedPage))))
                       .Returns(() => expectedPage);
Paul
  • 12,392
  • 4
  • 48
  • 58
1

Using Moq's It.IsAny<> without a .CallBack forces you to write code that's not covered by your test. Instead, it allows any query/expression at all to pass through, rendering your mock basically useless from a unit testing perspective.

The solution: You either need to use a Callback to test the expression OR you need to constrain your mock better. Either way is messy and difficult. I've dealt with this issue for as long as I've been practicing TDD. I finally threw together a helper class to make this a lot more expressive and less messy. Here's an example of one possible end-result:

mockPeopleRepository
  .Setup(x => x.Find(ThatHas.AnExpressionFor<Person>()
    .ThatMatches(correctPerson)
    .And().ThatDoesNotMatch(deletedPerson)
    .Build()))
  .Returns(_expectedListOfPeople); 

Here's the blog article that talks about it and gives the source code: http://awkwardcoder.com/2013/04/24/constraining-mocks-with-expression-arguments/

Byron Sommardahl
  • 12,743
  • 15
  • 74
  • 131