0

I want to mock a specific expression in one of my repositories and I'm having some trouble.

I currently have:

Mock<Container> returnContainer = new Mock<Container>();
Mock<IRepository<Container>> CntnrRepository =
    new Mock<IRepository<Container>>();

CntnrRepository.Setup<Container>(repo => repo
    .Find(x => x.Name == "foo")
    .Returns(returnContainer.Object);

Whenever the following code runs it's returning null instead of my Mock<Container> above.

Container found = 
    containerRepository.Find(x => x.Name == cntnrName);

What am I doing wrong here?

Edit

Below is the code that is using the injected repository:

public int Foo(Guid id, string name)
{
    Container found = 
        containerRepository.Find(x => x.Name == name);

    if (found != null)
        return CONTAINER_NOT_FREE;

    Container cntnrToAssociate =
        containerRepository.Find(x => x.Id == cntnrId);

    if (cntnrToAssociate == null)
        return CONTAINER_NOT_FOUND;

    return OK;
}

In the code above for one of my tests I need to return a value only in the first query (Find) to the containerRepository

Steven
  • 166,672
  • 24
  • 332
  • 435
Cole W
  • 15,123
  • 6
  • 51
  • 85

4 Answers4

4

Edit: i updated the solution, this works with Expression-arguments Edit2: i added a more general solution (look at the last test) which uses the ExpressionComparer from IQToolkit. That should satisfy any generic expression in a setup

If you set it up as shown below you can return not null only for some input arguments

    [Test]
    public void SetupFunc_TestWithExpectedArgument_ReturnsNotNull()
    {
        // Arrange
        var repository = new Mock<IRepository<Container>>();
        repository.Setup(r => r.Find(It.Is<Expression<Func<Container, bool>>>(x => NameIsFoo(x)))).Returns(new Container());

        // Act
        Container container = repository.Object.Find(x => x.Name == "foo");

        // Assert
        Assert.That(container, Is.Not.Null);
    }

    [Test]
    public void SetupFunc_TestWithOtherargument_ReturnsNull()
    {
        // arrange
        var repository = new Mock<IRepository<Container>>();
        repository.Setup(r => r.Find(It.Is<Expression<Func<Container, bool>>>(x => NameIsFoo(x)))).Returns(new Container());

        // Act
        Container container = repository.Object.Find(x => x.Name == "bar");

        // Assert
        Assert.That(container, Is.Null);
    }

    private static bool NameIsFoo(Expression<Func<Container, bool>> expression)
    {
        if (expression == null)
            return false;

        var mExpr = expression.Body as BinaryExpression;

        if (mExpr == null)
            return false;

        var constantExpression = mExpr.Right as ConstantExpression;

        if (constantExpression == null)
            return false;

        return Equals(constantExpression.Value, "foo");
    }

    [Test]
    public void SetupFunc_TestWithExpectedArgumentUsingExpressionComparer_ReturnsNotNull()
    {
        // Arrange
        var repository = new Mock<IRepository<Container>>();

        Expression<Func<Container, bool>> expectedArgument = x => x.Name == "foo";

        repository.Setup(r => r.Find(It.Is<Expression<Func<Container, bool>>>(x => ExpressionComparer.AreEqual(x, expectedArgument)))).Returns(new Container());

        // Act
        Container container = repository.Object.Find(x => x.Name == "foo");

        // Assert
        Assert.That(container, Is.Not.Null);
    }
Marius
  • 9,208
  • 8
  • 50
  • 73
  • I get `cannot convert from 'System.Func' to 'System.Linq.Expressions.Expression>'` when I try to compile that. – Cole W Jan 27 '12 at 01:26
  • I compiled it and ran it, works. Which version of moq are you using? – Marius Jan 27 '12 at 05:44
  • Aha, I didn't see that your repository parameter was an Expression, mine is only Func<>. I'll look at it later – Marius Jan 27 '12 at 07:01
  • +1 Thanks for the help here Marius but this isn't quite the answer I was looking for. I really appreciate your help on this. – Cole W Jan 28 '12 at 03:15
  • My last take on this :-) I've added a test which shows you how you can use a comparer to setup any expression. You can probably wrap this up in a helper to ease the verbosity. – Marius Jan 28 '12 at 22:03
0

Following seems to return non null object:

    static void Main(string[] args)
    {
        Mock<Container> returnContainer = new Mock<Container>();
        var CntnrRepository = new Mock<IRepository<Container>>();

        CntnrRepository.Setup<Container>(repo => repo.Find(x => x.Name == "foo")).Returns(returnContainer.Object);

        var found = CntnrRepository.Object.Find(x => x.Name == "foo");

        // Or if you want to pass the mock repository to a method
        var container = GetContainer(CntnrRepository.Object);
    }

    public static Container GetContainer(IRepository<Container> container)
    {
        return container.Find(x => x.Name == "foo");
    }

Instead of calling containerRepository.Find you have to call containerRepository.Object.Find. I wasn't even able to compile the code if I dropped the .Object part.

Edit: I added example how to pass the IRepository to a method

Toni Parviainen
  • 2,217
  • 1
  • 16
  • 15
  • Does this work whenever you pass a string into the method `GetContainer` to put into the `Find` method? `public static Container GetContainer(IRepository container, string name)` – Cole W Jan 23 '12 at 01:08
  • I just tested this and it returns null whenever you are using a variable in the linq expression. – Cole W Jan 23 '12 at 01:11
0

It appears that the answer is "it can't be done."

See Jason Punyon's answer to Moq.Mock - how to setup a method that takes an expression.

As per his answer, you can use

CntnrRepository.Setup(repo => repo.FindAll(
    It.IsAny<Expression<Func<Container, bool>>>()))
    .Returns(returnContainer.Object);

but you can't put any constraints on the expression. I suppose you could parse the expression using It.Is, but that's likely more trouble than it's worth for a mock.

This is one of several reasons why I am in the camp of "don't expose IQueryable on repositories." While you're only exposing IList, using Expression is going down the same slippery slope IMHO.

Community
  • 1
  • 1
TrueWill
  • 25,132
  • 10
  • 101
  • 150
  • 1
    Well I found the following article that looked promising but I can't get it to work. Apparently he got it to work but I didn't have much luck: http://www.codethinked.com/comparing-simple-lambda-expressions-with-moq – Cole W Jan 28 '12 at 02:15
  • Nice article! You might try the link Jimmy Bogard posted in the comments - the ExpressionComparer of http://iqtoolkit.codeplex.com/ – TrueWill Jan 28 '12 at 03:42
  • I did find a workaround for now. I posted it as an answer above. I will look into that code when I get a chance. Thanks. – Cole W Jan 28 '12 at 03:46
0

Workaround

I haven't found the solution I'm looking for as of now but I did find a decent workaround. I ended up using Moq's Callback method to do what I want. Please see the following:

int numCalls = 0;
List<Container> expectedContainers = new List<Container>();

//Add list of expected containers here.  Since I want the first call to return null
//and the 2nd call to return a valid container object I will fill the array with null
//in the 1st entry and a valid container in the 2nd
expectedContainers.Add(null);
expectedContainers.Add(new Container());

CntnrRepository.Setup<Container>(
                repo => repo.Find(It.IsAny<Expression<Func<Container, bool>>>()))
                .Returns(() => returnContainers[numCalls])
                .Callback(() => numCalls++);

The above code allows me to return different results at different times from the same repository.

So instead of looking for looking for specific linq expressions I return the correct objects based on the number of calls the function makes to the repository.

Cole W
  • 15,123
  • 6
  • 51
  • 85