Moqing 方法,其中 Expression>作为参数传入

发布于 2024-10-20 11:55:31 字数 1661 浏览 3 评论 0 原文

我对单元测试和模拟非常陌生!我正在尝试编写一些单元测试,其中涵盖一些与数据存储交互的代码。数据访问由 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() 进行调试,结果显示,existingCompany 始终为 null。在绝望中,我发现如果我更新 SignupLogic.AddNewCompany() ,以便对 FindBy 的调用如下所示:

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

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

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

从我读到的所有内容来看,似乎用 Moq 来测试我正在尝试的表达式实际上是不可能的。是否可以?请帮忙!

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:

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:

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. 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". 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". This test isn't passing.

Debugging through the unit test and AddNewCompany() as it runs, it would appear that existingCompany is always null. In desperation, I've found that if I update SignupLogic.AddNewCompany() so that the call to FindBy looks like this:

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. Obviously that's not especially useful in testing that any duplicate company is rejected by SignupLogic.AddNewCompany.

I've tried setting up moq.FindBy(...) to use "Is.ItAny", but that doesn't cause the test to pass either.

From everything I'm reading, it would appear that testing Expressions as I'm trying to isn't actually do-able with Moq here. Is it possible? Please help!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

浅黛梨妆こ 2024-10-27 11:55:31

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

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

... 中,您可以使用 predicate< /code> 返回匹配的公司(如果匹配的公司不是您所期望的,甚至可能抛出异常)。不是很漂亮,但我认为它会起作用。

It is probably correct that only an Expression with the exactly same structure (and literal values) will match. I suggest that you use the overload of Returns() that lets you use the parameters the mock is called with:

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). Not very pretty, but I think it will work.

葬﹪忆之殇 2024-10-27 11:55:31

您应该能够使用 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());

You should be able to use It.IsAny<>() to accomplish what you are looking to do. 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<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());
ヅ她的身影、若隐若现 2024-10-27 11:55:31

您通常只嘲笑您拥有的类型。那些不属于你的,真的不应该因为种种困难而被嘲笑。因此,模拟表达式 - 正如你的问题的名称所暗示的那样 - 不是正确的方法。

在起订量框架中。重要的是要为函数添加 .Returns() ,否则将不匹配。所以,如果你还没有这样做,那就是你的问题了。

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

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. It is important to put .Returns() for functions otherwise it is not matched. So if you have not done that, it is your problem.

repoMock.Setup(moq => moq.FindBy(c => c.Name == "Company Inc").Returns(....
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文