Moq'ing方法,其中Expression <Func <T,bool >>作为参数传递


67

我是单元测试和模拟的新手!我正在尝试编写一些单元测试,其中涉及与数据存储交互的一些代码。数据访问由IRepository封装:

interface IRepository<T> {
    ....
    IEnumerable<T> FindBy(Expression<Func<T, bool>> predicate);
    ....
}

我尝试使用IRepository的IoC具体实现来测试的代码如下:

public class SignupLogic {
    private Repository<Company> repo = new Repository<Company>();

    public void AddNewCompany(Company toAdd) {
        Company existingCompany = this.repo.FindBy(c => c.Name == toAdd.Name).FirstOrDefault();

        if(existingCompany != null) {
            throw new ArgumentException("Company already exists");
        }

        repo.Add(Company);
        repo.Save();
    }
}

为了测试SignupLogic.AddNewCompany()本身的逻辑,而不是逻辑和具体的存储库,我在模拟IRepository并将其传递给SignupLogic。模拟的存储库如下所示:

Mock<Repository> repoMock = new Mock<Repository>();
repoMock.Setup(moq => moq.FindBy(c => c.Name == "Company Inc")....

它返回一个内存中的IEnumberable,其中包含名称设置为“ Company Inc”的Company对象。调用SignupLogic.AddNewCompany的单元测试使用重复的详细信息设置了一家公司,并尝试将其传递给我,并且我断言ArgumentException随消息“公司已存在”引发。该测试未通过。

通过运行单元测试和AddNewCompany()进行调试,似乎现存的公司始终为null。无奈之下,我发现如果我更新SignupLogic.AddNewCompany(),则对FindBy的调用如下所示:

Company existingCompany = this.repo.FindBy(c => c.Name == "Company Inc").FirstOrDefault();

测试通过,这向我表明Moq仅响应与我在测试装置中设置的代码完全相同的代码。显然,这在测试任何重复的公司被SignupLogic.AddNewCompany拒绝时并不是特别有用。

我尝试将moq.FindBy(...)设置为使用“ Is.ItAny”,但这不会导致测试通过。

从我正在阅读的所有内容看来,在这里尝试测试Expressions实际上不是可以用Moq完成的。可能吗?请帮忙!


如果您的目标只是测试返回值,则可以强制Mock框架忽略该参数:Mock.Arrange(()=> repo.AllIn included(x => x.Category == category)).IgnoreArguments().Returns (((新列表<产品> {新产品()}).AsQueryable()); 在上面的示例中,我使用的是JustMock的免费版本。
Fernando Vezzali 2013年

Answers:


86

Expression具有完全相同的结构(和文字值)的匹配可能是正确的。我建议您使用的重载Returns(),让您使用调用模拟的参数:

repoMock.Setup(moq => moq.FindBy(It.IsAny<Expression<Func<Company, bool>>>())
        .Returns((Expression<Func<Company, bool>> predicate) => ...);

在中...,您可以predicate用来返回匹配的公司(如果匹配的公司不是您期望的,甚至可能会引发异常)。不是很漂亮,但是我认为它将起作用。


这给了我这样做的方法,至少可以为UniqueCompanyAccepted和DuplicateCompanyRejected测试获得一些绿灯:)
jonsidnell 2011年

1
这正是我所追求的,但是根据您在Aasmund上方的回答中的评论,“ ...”部分可能会进一步扩展?即实际上尝试匹配被传递到表达式中的公司吗?
IbrarMumtaz 2012年

7
@IbrarMumtaz:如果您有一个companies包含所有公司的列表,则.Returns((Expression<Func<Company, bool>> predicate) => companies.Where(predicate));应该可以使用。但是,如果您需要比此功能更高级的功能,则最好编写一个实现IRepository<Company>而不是使用Moq的类,因为在模拟中构建复杂的行为很麻烦。
Aasmund Eldhuset 2012年

7

您应该能够使用它It.IsAny<>()来完成您想要做的事情。使用,It.IsAny<>()您只需调整设置的返回类型即可测试代码的每个分支。

It.IsAny<Expression<Func<Company, bool>>>()

首次测试,不管谓词如何返回公司,这都会引发异常:

var repoMock = new Mock<IRepository<Company>>();
repoMock.Setup(moq => moq.FindBy(It.IsAny<Expression<Func<Company, bool>>>())).Returns(new List<Company>{new Company{Name = "Company Inc"}});
var signupLogic = new SignupLogic(repoMock.Object);
signupLogic.AddNewCompany(new Company {Name = "Company Inc"});
//Assert the exception was thrown.

第二次测试,将返回类型设置为空列表,这将导致add被调用。

var repoMock = new Mock<IRepository<Company>>();
repoMock.Setup(moq => moq.FindBy(It.IsAny<Expression<Func<Company, bool>>>())).Returns(new List<Company>());
var signupLogic = new SignupLogic(repoMock.Object);
signupLogic.AddNewCompany(new Company {Name = "Company Inc"});
repoMock.Verify(r => r.Add(It.IsAny<Company>()), Times.Once());

仅在将另一个问题标记为答案后才看到此消息,但也会有所帮助。谢谢!
jonsidnell

6
It.IsAnyReturn()不检查参数的a组合使用的缺点是,您无法验证参数是否正确。当然,有时候,您确实确实希望接受任何参数值,但是在这种情况下,您可能想要验证是否SignupLogic正确构造了它的查询(使用公司名称,而不是其他属性)。
2011年

It.IsAny将永远过去。我更喜欢检查lambda表达式是否相等m.FindBy(item=>item.CompanyID == 1)。完成此操作的一种方法是使用LambdaCompare:stackoverflow.com/questions/283537/…。通过使用此比较类,新的设置将为Expression<Func<Company, bool>> testExpression = item => item.CompanyID == 1;repoMock.Setup(m => m.FindById(It.Is<Expression<Func<Company, bool>>>(criteria => LambdaCompare.Eq(criteria, testExpression)))).Returns(yourCompanyList)
JuniorMayhé'Sep

2

通常,您只模拟自己拥有的类型。那些您不拥有的人,真的不应该因为各种困难而被嘲笑。因此,嘲笑表达式(正如您的问题的名称所暗示的那样)不是解决之道。

在Moq框架中。重要的是要放置.Returns()功能,否则功能将不匹配。因此,如果您还没有这样做,那就是您的问题。

repoMock.Setup(moq => moq.FindBy(c => c.Name == "Company Inc").Returns(....

4
嘲笑自己拥有的一种Repository。那个Expression不是假的。它仅用于匹配模拟上的调用。
Aasmund Eldhuset 2011年

问题的标题是Moq'ing Expression<Func<T, bool>> parameters
Aliostad

公平的评论Aliostad,标题尚不清楚,尽管我想认为其余问题使我的意思很清楚-毕竟这是一个相当详细的,经过代码抽样的问题,即使我自己也这样说:)因此,问题实际上是“向表达式<Func <T,bool>参数添加”,在这里我试图表明它是将表达式作为参数传递给方法的地方。但是,我已经编辑了标题以使其更清晰:)
jonsidnell

1
我尝试过这种方法,但是当我使用验证时,它不起作用:结果消息:Moq.MockException:预期对模拟调用一次,但被调用0次:u => u.Users.Any(a => a.Email = = .user.Email)配置的设置:u => u.Users.Any(a => a.Email == .user.Email),Times.Never
山本晃

1
@Aliostad您的答案很旧,但无法正常工作。Moq为Expression返回UnsupportedException的异常。表达式必须以“It.Is/It.IsAny”传递
EL-PROVA
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.