在存储库中模拟特定表达式

时间:2012-01-21 05:29:28

标签: c# unit-testing dependency-injection repository moq

我想在我的一个存储库中模拟一个特定的表达式,但我遇到了一些麻烦。

我目前有:

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);

每当下面的代码运行时,它返回null而不是上面的Mock<Container>

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

我在这里做错了什么?

修改

以下是使用注入存储库的代码:

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;
}

在我的一个测试的上面代码中,我只需要在第一个查询(Find)中将值返回到containerRepository

4 个答案:

答案 0 :(得分:4)

编辑:我更新了解决方案,这适用于Expression-arguments Edit2:我添加了一个使用IQToolkit中的ExpressionComparer的更通用的解决方案(查看最后一个测试)。这应该满足设置

中的任何通用表达式

如果按如下所示进行设置,则只能为某些输入参数返回

    [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);
    }

答案 1 :(得分:0)

以下似乎返回非null对象:

    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");
    }

您必须调用containerRepository.Object.Find,而不是调用containerRepository.Find。如果删除.Object部分,我甚至无法编译代码。

编辑:我添加了如何将IRepository传递给方法

的示例

答案 2 :(得分:0)

似乎答案是“无法完成。”

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

根据他的回答,你可以使用

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

但你不能对表达式施加任何约束。我想你可以使用It.Is解析表达式,但这可能比模拟它的价值更麻烦。

这是我参加“don't expose IQueryable on repositories”阵营的几个原因之一。虽然你只是暴露了IList,但是使用Expression会下滑到相同的滑坡IMHO。

答案 3 :(得分:0)

解决方法

到目前为止,我还没有找到我正在寻找的解决方案,但我找到了一个不错的解决方法。我最终使用Moq的Callback方法来做我想做的事。请参阅以下内容:

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++);

上面的代码允许我在同一个存储库中的不同时间返回不同的结果。

因此,我没有寻找特定的linq表达式,而是根据函数对存储库的调用次数返回正确的对象。