简体   繁体   English

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

[英]Moq'ing methods where Expression<Func<T, bool>> are passed in as parameters

I'm very new to unit testing and mocking! 我对单元测试和嘲笑很新! I'm trying to write some unit tests that covers some code that interacts with a data store. 我正在尝试编写一些单元测试,其中包含一些与数据存储交互的代码。 Data access is encapsulated by IRepository: 数据访问由IRepository封装:

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

The code that I'm trying to test, utilising a concrete IoC'd implementation of IRepository looks like this: 我正在尝试使用具体的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();
    }
}

So that I'm testing the logic of SignupLogic.AddNewCompany() itself, rather than the logic and the concrete Repository, I'm mocking up IRepository and passing it into SignupLogic. 因此,我正在测试SignupLogic.AddNewCompany()本身的逻辑,而不是逻辑和具体的存储库,我正在模拟IRepository并将其传递给SignupLogic。 The mocked up Repository looks like this: 模拟的存储库看起来像这样:

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

which returns an in-memory IEnumberable containing a Company object with name set to "Company Inc". 它返回一个内存中的IEnumberable,其中包含名称设置为“Company Inc”的Company对象。 The unit test that calls SignupLogic.AddNewCompany sets up a company with duplicate details and trys to pass that in, and I assert that an ArgumentException is thrown with the message "Company already exists". 调用SignupLogic.AddNewCompany的单元测试会设置一个具有重复详细信息和trys的公司来传递它,并且我断言抛出了ArgumentException,并显示消息“公司已存在”。 This test isn't passing. 这个测试没有通过。

Debugging through the unit test and AddNewCompany() as it runs, it would appear that existingCompany is always null. 在运行时通过单元测试和AddNewCompany()进行调试,似乎existingCompany始终为null。 In desperation, I've found that if I update SignupLogic.AddNewCompany() so that the call to FindBy looks like this: 无奈之下,我发现如果我更新SignupLogic.AddNewCompany()以便对FindBy的调用如下所示:

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

the test passes, which suggests to me that Moq is only responding to code that is exactly the same as I've setup in my test fixture. 测试通过,这告诉我,Moq只响应与我在测试夹具中设置的代码完全相同的代码。 Obviously that's not especially useful in testing that any duplicate company is rejected by SignupLogic.AddNewCompany. 显然,在测试任何重复的公司被SignupLogic.AddNewCompany拒绝时,这并不是特别有用。

I've tried setting up moq.FindBy(...) to use "Is.ItAny", but that doesn't cause the test to pass either. 我已经尝试设置moq.FindBy(...)来使用“Is.ItAny”,但这并不会导致测试通过。

From everything I'm reading, it would appear that testing Expressions as I'm trying to isn't actually do-able with Moq here. 从我正在阅读的所有内容来看,似乎我正在尝试测试表达式实际上并不能在这里使用Moq。 Is it possible? 可能吗? Please help! 请帮忙!

It is probably correct that only an Expression with the exactly same structure (and literal values) will match. 只有具有完全相同结构(和文字值)的Expression匹配才可能是正确的。 I suggest that you use the overload of Returns() that lets you use the parameters the mock is called with: 我建议您使用Returns()的重载,它允许您使用调用mock的参数:

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

In ... , you can use predicate to return the matching companies (and maybe even throw an exception if the matching companies isn't what you expected). ... ,您可以使用predicate来返回匹配的公司(如果匹配的公司不符合您的预期,甚至可能会抛出异常)。 Not very pretty, but I think it will work. 不是很漂亮,但我认为它会起作用。

You should be able to use It.IsAny<>() to accomplish what you are looking to do. 您应该能够使用It.IsAny<>()来完成您要执行的操作。 With the use of It.IsAny<>() you can simply adjust the return type for your setup to test each branch of your code. 通过使用It.IsAny<>()您只需调整设置的返回类型即可测试代码的每个分支。

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

First Test, return a company regardless of predicate which will cause the exception to throw: 第一次测试,返回公司而不管谓词会导致异常抛出:

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.

Second Test, make the return type an empty list witch will cause add to be called.: 第二次测试,使返回类型为空列表将导致添加被调用:

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

You normally only mock the types you own. 您通常只会模拟您拥有的类型。 Those you do not own, really should not be mocked because of various difficulties. 那些你不拥有的人,真的不应该因各种困难而被嘲笑。 So mocking expressions - as the name of your question implies - is not the way to go. 所以嘲弄表达 - 正如你的问题所暗示的那样 - 不是要走的路。

In Moq framework. 在Moq框架中。 It is important to put .Returns() for functions otherwise it is not matched. .Returns()放在函数中是很重要的,否则就不匹配了。 So if you have not done that, it is your problem. 所以,如果你还没有这样做,那就是你的问题。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM