C# 模拟存储库和测试参数化服务方法

C# 模拟存储库和测试参数化服务方法,c#,unit-testing,mocking,expression,moq,C#,Unit Testing,Mocking,Expression,Moq,我花了几天时间寻找解决方案,它允许我模拟由表达式参数化的方法。我找到了。但不幸的是,当我想用字符串参数测试服务方法时,它不起作用,例如:public IEnumerable FindByName(字符串名称),如下所示: using System; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using Microsoft.VisualStudio.TestTools.UnitTestin

我花了几天时间寻找解决方案,它允许我模拟由
表达式
参数化的方法。我找到了。但不幸的是,当我想用字符串参数测试服务方法时,它不起作用,例如:
public IEnumerable FindByName(字符串名称)
,如下所示:

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));
        }
    }
}
使用系统;
使用系统诊断;
使用System.Linq;
使用System.Linq.Expressions;
使用Microsoft.VisualStudio.TestTools.UnitTesting;
使用最小起订量;
名称空间UnitTestProject
{
[测试类]
公共类UnitTest1
{
[测试方法]
公共void TestMethod1()
{
var mock=new mock r.Find(AreEqual(p=>p.FirstName.Equals(“Justin”)))).Returns(new[]
{
新人{FirstName=“Justin”,LastName=“Smith”},
新人{FirstName=“Justin”,LastName=“Quincy”}
});
var personService=newpersonservice(mock.Object);
Person[]justins=personService.FindByName(“Justin”).ToArray();
Person[]Etheredge=personService.FindByName(“Etheredge”).ToArray();
Debugger.Break();
}
静态表达式AreEqual(表达式表达式表达式)
{
返回Match.Create(t=>t.ToString()==expr.ToString());
}
}
公共阶层人士
{
公共字符串名{get;set;}
公共字符串LastName{get;set;}
}
公共接口假定
{
IEnumerable Find(表达式谓词);
}
公共类人员服务
{
只读存储库;
公共人员服务(IRepository存储库)
{
_存储库=存储库;
}
公共IEnumerable FindByName(字符串名称)
{
返回_repository.Find(p=>p.FirstName.Equals(name));
}
}
}
当调试器中断时,我希望数组
justins
将包含上面列出的两个项,而数组
etheredges
将不包含任何项。实际上,它们都是空数组。我怀疑这是因为在
FindByName
方法中,字符串不是直接提供的,而是通过变量
name
提供的


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

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

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

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

**

需要考虑的是,您的服务有知识库的内部工作,因为它通过文字lambda表达式传递。您可以做的是将表达式从服务中抽象出来,并将其与存储库实现更紧密地关联起来,从而使一切都变得坚实。如果您决定插入一个EnterprisePerson存储库,该存储库调用一个寻找此人的WCF服务,会发生什么情况

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

**


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

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

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

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

**

需要考虑的是,您的服务有知识库的内部工作,因为它通过文字lambda表达式传递。您可以做的是将表达式从服务中抽象出来,并将其与存储库实现更紧密地关联起来,从而使一切都变得坚实。如果您决定插入一个EnterprisePerson存储库,该存储库调用一个寻找此人的WCF服务,会发生什么情况

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

**


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

假设您只想测试服务的
查找
逻辑(并且您信任LINQ:-),那么什么
  [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());
  }
   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));
      }
   }