【问题标题】:Mocking repository and testing parametrized service method模拟存储库和测试参数化服务方法
【发布时间】:2014-04-23 17:39:51
【问题描述】:

我花了几天时间寻找允许我模拟由Expression<Func<T, bool>> 参数化的方法的解决方案。我找到了this。但不幸的是,当我想用​​字符串参数测试服务方法时它不起作用,例如:public IEnumerable<Person> FindByName(string name),如下所示:

using System;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace UnitTestProject
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
            var mock = new Mock<IRepository<Person>();

            mock.Setup(r => r.Find(AreEqual<Person>(p => p.FirstName.Equals("Justin")))).Returns(new[]
                {
                    new Person {FirstName = "Justin", LastName = "Smith"},
                    new Person {FirstName = "Justin", LastName = "Quincy"}
                });

            var personService = new PersonService(mock.Object);
            Person[] justins = personService.FindByName("Justin").ToArray();
            Person[] etheredges = personService.FindByName("Etheredge").ToArray();

            Debugger.Break();
        }

        static Expression<Func<T, bool>> AreEqual<T>(Expression<Func<T, bool>> expr)
        {
            return Match.Create<Expression<Func<T, bool>>>(t => t.ToString() == expr.ToString());
        }
    }

    public class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

    public interface IRepository<T>
    {
        IEnumerable<T> Find(Expression<Func<Person, bool>> predicate);
    }

    public class PersonService
    {
        readonly IRepository<Person> _repository;

        public PersonService(IRepository<Person> repository)
        {
            _repository = repository;
        }

        public IEnumerable<Person> FindByName(string name)
        {
            return _repository.Find(p => p.FirstName.Equals(name));
        }
    }
}

当调试器中断时,我预计数组 justins 将包含上面列出的两项,而数组 etheredges 将不包含任何项。实际上它们都是空数组。我怀疑这是因为在FindByName 方法中,字符串不是直接提供的,而是通过变量name 提供的。

你知道如何解决这个问题吗?

【问题讨论】:

  • 不清楚你想模拟什么以及你想测试什么。您想模拟表达式以测试使用它的类吗?或者你想模拟这个类来测试表达式?
  • 我想模拟存储库的方法,从 db 返回通过提供的表达式的项目,并且我想测试更高级别的服务方法,该方法找到所有提供的名称作为字符串的项目。
  • 您需要发布您的存储库代码才能收到答案。

标签: c# unit-testing mocking expression moq


【解决方案1】:

假设您只想测试服务的 Find 逻辑(并且您信任 LINQ :-)),您可以做的只是编译传入的 Expression 谓词并跨假存储库执行表达式(即pred =&gt; fakePeople.Where(pred.Compile()));):

  [TestMethod]
  public void TestMethod1()
  {
     var mock = new Mock<IRepository<Person>>();

     var fakePeople = new[]
          {
             new Person {FirstName = "Justin", LastName = "Smith"},
             new Person {FirstName = "Justin", LastName = "Quincy"},
             new Person {FirstName = "Joe", LastName = "Bloggs"}
          };

     mock.Setup(r => r.Find(It.IsAny<Expression<Func<Person, bool>>>()))
         .Returns<Expression<Func<Person, bool>>>(
             pred => fakePeople.Where(pred.Compile()));

     var personService = new PersonService(mock.Object);

     var searchForJustins = personService.FindByName("Justin");
     Assert.AreEqual(2, searchForJustins.Count());
     Assert.IsTrue(searchForJustins.Any(_ => _.LastName == "Quincy") 
           && searchForJustins.Any(_ => _.LastName == "Smith"));

     var searchForEtheredges = personService.FindByName("Etheredge");
     Assert.IsFalse(searchForEtheredges.Any());
  }

次要,但存储库代码本身没有编译 - 我假设你有一个通用的 repo 模式:

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

   public class PersonService
   {
      readonly IRepository<Person> _repository;

      public PersonService(IRepository<Person> repository)
      {
         _repository = repository;
      }

      public IEnumerable<Person> FindByName(string name)
      {
         return _repository.Find(p => p.FirstName.Equals(name));
      }
   }

【讨论】:

  • 效果很好! :D 我想知道,当 Moq 如此漂亮地解决这个问题时,人们怎么可能想到像我的问题中那样的糟糕解决方法:) 非常感谢!
  • 我刚刚修正了我的错误,代码现在可以编译了 ;)
【解决方案2】:

问题是您的设置与表达式参数不匹配。这是测试使用文字 lambda 表达式的类的困难。你不能真正将一个委托与另一个委托匹配,除非通过匹配参数和返回类型。

即使表达式在存储库中执行,服务也拥有表达式,并且服务测试夹具应测试表达式是否产生正确的结果。通过设置匹配表达式,您基本上是在构建一个复杂的变更控制测试。

要正确测试这一点,您必须将数据放入存储库,让存储库在数据上运行表达式(其本身可以被模拟或在内存中),并断言您获得了预期的数据作为回报。

**

需要考虑的其他一点是,您的服务通过传递文字 lambda 表达式来了解存储库的内部工作。您可以做的就是将表达式从服务中抽象出来,并将其与存储库实现更紧密地关联起来。如果您决定插入调用 WCF 服务来查找 Person 的 EnterprisePersonRepository,会发生什么?

我会通过静态属性将表达式添加到存储库实现中,并使用您的 IOC 容器来引用它。因此,当您使用 IRepository 注册您的 PersonRepository 时,您还将注册 FindByName 表达式。

**

最后一个想法是,这种类型的超抽象和对 OO 教条的坚持在非教学环境中可能会弄巧成拙。在您寻找该问题的解决方案的几天内,本可以编写大量简单且经过良好测试的代码。

【讨论】:

  • 非常有成就感……你能举一些例子来说明你的想法吗?
猜你喜欢
  • 2021-02-13
  • 1970-01-01
  • 1970-01-01
  • 2021-03-16
  • 1970-01-01
  • 2018-12-22
  • 1970-01-01
  • 1970-01-01
  • 2019-08-17
相关资源
最近更新 更多