2011-04-13 23 views
7

我在当前正在开发的MVC 3应用程序中使用存储库模式。我的仓库界面看起来如下:NSubstitute - 测试特定的linq表达式

public interface IRepository<TEntity> where TEntity : IdEntity 
{ 
    void Add(TEntity entity); 
    void Update(TEntity entity); 
    void Remove(TEntity entity); 
    TEntity GetById(int id); 
    IList<TEntity> GetAll(); 
    TEntity FindFirst(Expression<Func<TEntity, bool>> criteria); 
    IList<TEntity> Find(Expression<Func<TEntity, bool>> criteria); 
} 

在许多实例中,当我在服务类的编码方法,我使用的是使用FindFirst和查找方法。正如你所看到的,它们都以linq表达式作为输入。我想知道的是,NSubstitute是否允许您在代码中指定要测试的特定表达式。

所以,这里是说明了使用存储库的方法之一的我已经提到的服务方法的一个例子:

public IList<InvoiceDTO> GetUnprocessedInvoices() 
    { 
     try 
     { 
      var invoices = _invoiceRepository.Find(i => !i.IsProcessed && i.IsConfirmed); 
      var dtoInvoices = Mapper.Map<IList<Invoice>, IList<InvoiceDTO>>(invoices); 
      return dtoInvoices; 
     } 
     catch (Exception ex) 
     { 
      throw new Exception(string.Format("Failed to get unprocessed invoices: {0}", ex.Message), ex); 
     } 
    } 

那么,有没有一种方法,使用Nsubtitute,我可以测试具体lamda表达式:(i =>!i.IsProcessed & & i.IsConfirmed)?

任何指导将不胜感激。

回答

12

非常简短的回答是否定的,NSubstitute没有任何内置它使测试具体表现更加容易。

长得多的答案是有,你可以尝试几个选项,其中大部分涉及避免被测类直接使用LINQ的。我不确定这些是否是好主意,因为我不知道整个背景,但希望在这里可以使用一些信息。在下面的例子中,我已经消除了Mapper步骤,以使代码样本更小一些。

第一种选择是让这样你就可以检查表达式是你期望的相同的参考,这意味着你不能再在你的代码中直接创建测试。例如:

//Class under test uses: 
_invoiceRepository.Find(Queries.UnprocessedConfirmedOrders) 

[Test] 
public void TestUnprocessedInvoices() 
{ 
    IList<InvoiceDTO> expectedResults = new List<InvoiceDTO>(); 
    _invoiceRepository.Find(Queries.UnprocessedConfirmedOrders).Returns(expectedResults); 
    Assert.That(_sut.GetUnprocessedInvoices(), Is.SameAs(expectedResults)); 
} 

我已经将表达式转储到静态查询类上,但您可以使用工厂来更好地封装它。由于您使用的是实际表达式的引用,因此可以设置返回值并检查正常接收的调用。您也可以单独测试表达式。

第二个选项通过使用规格模式进一步考虑了这一点。说你下面的成员添加到IRepository接口,并引入ISpecification:

public interface IRepository<TEntity> where TEntity : IdEntity 
{ 
    /* ...snip... */ 
    IList<TEntity> Find(ISpecification<TEntity> query); 
} 

public interface ISpecification<T> { bool Matches(T item); } 

然后,您可以测试它是这样的:

//Class under test now uses: 
_invoiceRepository.Find(new UnprocessedConfirmedOrdersQuery()); 

[Test] 
public void TestUnprocessedInvoicesUsingSpecification() 
{ 
    IList<InvoiceDTO> expectedResults = new List<InvoiceDTO>(); 
    _invoiceRepository.Find(Arg.Any<UnprocessedConfirmedOrdersQuery>()).Returns(expectedResults); 
    Assert.That(_sut.GetUnprocessedInvoices(), Is.SameAs(expectedResults)); 
} 

同样,你可以隔离测试此查询,以确保它做你的想法。

第三种方法是捕捉使用的参数并直接对其进行测试。这是一个有点乱,但工程:

[Test] 
public void TestUnprocessedInvoicesByCatchingExpression() 
{ 
    Expression<Func<InvoiceDTO, bool>> queryUsed = null; 
    IList<InvoiceDTO> expectedResults = new List<InvoiceDTO>(); 
    _invoiceRepository 
     .Find(i => true) 
     .ReturnsForAnyArgs(x => 
     { 
      queryUsed = (Expression<Func<InvoiceDTO, bool>>)x[0]; 
      return expectedResults; 
     }); 

    Assert.That(_sut.GetUnprocessedInvoices(), Is.SameAs(expectedResults)); 
    AssertQueryPassesFor(queryUsed, new InvoiceDTO { IsProcessed = false, IsConfirmed = true }); 
    AssertQueryFailsFor(queryUsed, new InvoiceDTO { IsProcessed = true, IsConfirmed = true }); 
} 

(希望这将让未来NSubstitute版本更容易一点)

第四个选项是要找到/借/写/窃取一些代码,可以比较表达式树,并使用NSubstitute的Arg.Is(...)来取得一个谓词来比较表达式树。

第五种方法是不单元测试它到那个程度,而只是使用真正的InvoiceRepository进行集成测试。不要担心所发生的事情的机制,请尝试验证您所需的实际行为。

我的一般建议是看看你需要测试什么,以及如何才能最好,最容易编写这些测试。请记住,表达式和它传递的事实都需要以某种方式进行测试,并且测试不需要是单元测试。也许值得考虑一下,当前的IRepository接口是否让你的生活更轻松。您可以尝试编写测试,比如,然后查看可以推出哪些设计来支持可测试性。

希望这会有所帮助。

+0

我一直在摔跤类似的东西 - 我真的很喜欢能够测试我所有的LINQ,但我发现很难,当你介绍这样的事情不能被执行SqlFunctions。 http://stackoverflow.com/questions/21495980/testing-sqlfunctions-in-linq-statements - 我想我只是去集成测试,而不是 – Neil 2014-02-01 09:21:16

3

当我试图找出如何在NSubstitute中使用lambda表达式返回特定值时,我偶然发现了这个问题。但是,对于我的用例,我不在乎实际传递给linq查询的内容,并且想要分享如何在NSubstitute中的模拟接口上返回linq查询的值。

因此,使用从上面

[Test] 
public void TestUnprocessedInvoices() 
{ 
    IList<InvoiceDTO> expectedResults = new List<InvoiceDTO>(); 
    _invoiceRepository.Find(Arg.Any<Expression<Func<Invoice, bool>>>()).Returns(expectedResults); 
} 
3

我不愿放弃对我的仓库界面使用Expression<Func<T,bool>>,所以作为替代编程这一个特定的模拟(因为NSubstitute不支持的例子它),我只是在测试夹具内创建了一个私有类,它实现了我的存储库接口,并且只创建了测试将使用的与表达式相关的方法。我可以像往常一样继续使用NSubstitute来模拟所有其他依赖项,但是我可以使用同一个存储库进行多个不同的测试,并从不同的输入中获得不同的结果。

public class SomeFixture 
{ 
    private readonly IRepository<SomeEntity> entityRepository; 
    private readonly IRepository<SomeThing> thingRepository; 

    public SomeFixture() 
    { 
     var entities = new List<SomeEntity> 
     { 
      BuildEntityForThing(1), 
      BuildEntityForThing(1), 
      BuildEntityForThing(1), 
      BuildEntityForThing(2), 
     }; 
     entityRepository = new FakeRepository(entities); 

     thingRepository = Substitute.For<IRepository<SomeThing>>(); 
     thingRepository.GetById(1).Returns(BuildThing(1)); 
     thingRepository.GetById(2).Returns(BuildThing(2)); 
    } 

    public void SomeTest() 
    { 
     var classUnderTest = new SomeClass(thingRepository, entityRepository); 

     Assert.AreEqual(classUnderTest.FetchEntitiesForThing(1).Count, 3); 
    } 

    private void SomeOtherTest() 
    { 
     var classUnderTest = new SomeClass(thingRepository, entityRepository); 

     Assert.AreEqual(classUnderTest.FetchEntitiesForThing(2).Count, 1); 
    } 

    private class FakeRepository : IRepository<SomeEntity> 
    { 
     private readonly List<SomeEntity> items; 

     public FakeRepository(List<SomeEntity> items) 
     { 
      this.items = items; 
     } 

     IList<TEntity> Find(Expression<Func<SomeEntity, bool>> criteria) 
     { 
      // For these purposes, ignore possible inconsistencies 
      // between Linq and SQL when executing expressions 
      return items.Where(criteria.Compile()).ToList(); 
     } 

     // Other unimplemented methods from IRepository ... 
     void Add(SomeEntity entity) 
     { 
      throw new NotImplementedException(); 
     } 
    } 
}