我有以下代码:
public class Foo
{
public string Name { get; set; }
}
//query object pattern
public class FooQuery
{
private string _startsWith;
public FooQuery NameStartsWith(string startsWith)
{
_startsWith = startsWith;
return this;
}
public List<Foo> Execute(IQueryable<Foo> someContext)
{
if (!string.IsNullOrWhiteSpace(_startsWith))
someContext = someContext.Where(f => f.Name.StartsWith(_startsWith));
return someContext.ToList();
}
}
public interface IFooService
{
List<Foo> FindByNameStartsWith(string startsWith);
}
public class FooService : IFooService
{
private readonly IFooRepository _fooRepository;
public FooService(IFooRepository fooRepository)
{
_fooRepository = fooRepository;
}
public List<Foo> FindByNameStartsWith(string startsWith)
{
var query = new FooQuery().NameStartsWith(startsWith);
return _fooRepository.Find(query);
}
}
public interface IFooRepository
{
List<Foo> Find(FooQuery query);
}
public class FooRepository : IFooRepository
{
public List<Foo> Find(FooQuery query)
{
var someContext = new List<Foo>().AsQueryable(); //would be EF/Mongo, etc
return query.Execute(someContext);
}
}
基本上,我有一个服务“FooService”,用于通知查询对象“FooQuery”并根据传递给它的方法参数设置其状态。然后,该服务将查询传递到存储库“FooRepository”,在那里它执行数据访问。 FooQuery故意不通过属性公开其状态。它反而暴露了更好控制的方法。我需要对FooService正确创建查询对象进行单元测试。
这是一个挑战,因为FooQuery的状态对于单元测试是不可见的。我看到了几个选项,但似乎都闻到了:
将FooQuery的状态公开为只读属性,并在单元测试中检查这些属性在传递到存储库时是否有效。 (通过使用回调模拟框架,检查查询状态在技术上是可行的。)我不喜欢这样,因为我们现在必须打开状态并修改代码仅用于测试目的。
保持代码不变,并测试从服务方法生成的结果与查询对象的结果相同。我不喜欢这样,因为它使单元测试更大,更少结论性和更多冗余(在检查结果时,我必须对查询对象本身进行非常类似的测试)
在界面中包装FooQuery并创建一个工厂以注入foo服务。然后,我可以测试在模拟查询上调用的正确方法。但是,这仍然让我对工厂本身进行了具有挑战性的测试。
任何有关测试/重新分解此代码以使其更易于测试的建议都将受到赞赏。
答案 0 :(得分:1)
您可以在Equals
上实施FooQuery
,然后通过模拟FooQuery
验证我是否获得了预期的IFooRepository
。这是限制形式您的选项#1。
答案 1 :(得分:1)
您似乎有两个职责:从数据源获取IQueryable,并对其执行查询。也许更简单的设计是有道理的?
public interface IFooRepository
{
IQueryable<Foo> GetFoo();
}
public FooService : IFooService
{
public List<Foo> FindByNameStartsWith(string startsWith)
{
return new FooQuery().StartsWith(startsWith).Execute(_fooRepo.GetFoo());
}
}
现在,FooService易于测试,数据库特定逻辑与所有其他逻辑分离。
[Test]
public void StartsWithFiltersFooFromRepository()
{
var fooFromRepository = new List<Foo> { new Foo {Name="yes1"}, new Foo {Name="no"}, new Foo {Name="yes2"} };
_fooRepMock.Setup(r=>r.GetFoo()).Returns(fooFromRepository);
var actual = _fooService.FindByNameStartsWith("yes");
Assert.That(actual, Is.EquivalentTo(new [] { fooFromRepository[0], fooFromRepository[2] }));
}