我花了几天寻找解决方案,这允许我模拟由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
提供的。
你知道如何解决这个问题吗?
答案 0 :(得分:3)
假设您只想测试服务的Find
逻辑(并且您信任LINQ :-)),您可以做的就是编译传入的Expression
谓词并执行表达式虚假存储库(即pred => 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());
}
轻微,但是Repository代码本身没有编译 - 我假设你有一个通用的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));
}
}
答案 1 :(得分:1)
问题是您的设置与表达式参数不匹配。这对于使用文字lambda表达式的测试类来说是一个难题。除了匹配参数并返回Type之外,您无法将一个委托与另一个委托真正匹配。
即使表达式在存储库中执行,服务也拥有表达式,服务测试夹具应测试表达式是否产生正确的结果。通过设置匹配表达式,您基本上构建了精细的变更控制测试。
要正确地测试这一点,您必须将数据放入存储库,让存储库在数据上运行表达式(它本身可以被模拟或在内存中),并声明您获得了预期的数据作为回报。 / p>
**
要考虑的其他事情是,您的服务通过传入文字lambda表达式了解存储库的内部工作。你可以做些什么来制作SOLID,就是将表达式从服务中抽象出来,并将它与存储库实现更紧密地联系起来。如果您决定插入调用WCF服务寻找Person的EnterprisePersonRepository,会发生什么?
我会通过静态属性将表达式添加到存储库实现,并使用您的IOC容器来引用它。因此,当您使用IRepository注册PersonRepository时,您还将注册FindByName表达式。
**
最后的想法是,在非教学环境中,这种超抽象和坚持OO教条可能会弄巧成拙。在您寻找解决此问题的解决方案的几天内,可以编写许多简单且经过良好测试的代码。