表达式<Func <T,bool >>的Moq'ing方法作为参数传入

jon*_*ell 60 c# unit-testing moq

我对单元测试和嘲笑很新!我正在尝试编写一些单元测试,其中包含一些与数据存储交互的代码.数据访问由IRepository封装:

interface IRepository<T> {
    ....
    IEnumerable<T> FindBy(Expression<Func<T, bool>> predicate);
    ....
}
Run Code Online (Sandbox Code Playgroud)

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

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,我正在测试SignupLogic.AddNewCompany()本身的逻辑,而不是逻辑和具体的存储库,我正在模拟IRepository并将其传递给SignupLogic.模拟的存储库看起来像这样:

Mock<Repository> repoMock = new Mock<Repository>();
repoMock.Setup(moq => moq.FindBy(c => c.Name == "Company Inc")....
Run Code Online (Sandbox Code Playgroud)

它返回一个内存中的IEnumberable,其中包含名称设置为"Company Inc"的Company对象.调用SignupLogic.AddNewCompany的单元测试会设置一个具有重复详细信息和trys的公司来传递它,并且我断言抛出了ArgumentException,并显示消息"公司已存在".这个测试没有通过.

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

Company existingCompany = this.repo.FindBy(c => c.Name == "Company Inc").FirstOrDefault();
Run Code Online (Sandbox Code Playgroud)

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

我已经尝试设置moq.FindBy(...)来使用"Is.ItAny",但这并不会导致测试通过.

从我正在阅读的所有内容来看,似乎我正在尝试测试表达式实际上并不能在这里使用Moq.可能吗?请帮忙!

Aas*_*set 74

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

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

...,你可以predicate用来返回匹配的公司(如果匹配的公司不符合你的预期,甚至可能会抛出异常).不是很漂亮,但我认为它会起作用.

  • @IbrarMumtaz:如果你有一个包含所有公司的"公司"列表,那么`.Returns((表达式<Func <公司,bool >>谓词)=> companies.Where(谓词));`应该有效.但是,如果您需要比此更高级的功能,那么编写一个实现`IRepository <Company>`而不是使用Moq的类可能会更好,因为将复杂的行为构建到模拟中是很麻烦的. (6认同)

Mar*_*man 7

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

It.IsAny<Expression<Func<Company, bool>>>()
Run Code Online (Sandbox Code Playgroud)

第一次测试,返回公司而不管谓词会导致异常抛出:

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.
Run Code Online (Sandbox Code Playgroud)

第二次测试,使返回类型为空列表将导致添加被调用:

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());
Run Code Online (Sandbox Code Playgroud)

  • 将`It.IsAny`与不检查参数的`Return()`结合使用的缺点是你无法验证参数是否应该是什么.有时,当然,您确实希望接受任何参数值,但在这种情况下,您可能需要验证`SignupLogic`是否正确构造其查询(使用公司名称,而不是其他某些属性). (5认同)