NSubstitute - 测试特定的linq表达式

Ozz*_*zzy 10 c# unit-testing nsubstitute

我正在使用我正在开发的MVC 3应用程序中的存储库模式.我的存储库界面如下所示:

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);
}
Run Code Online (Sandbox Code Playgroud)

在很多实例中,当我在服务类中编码方法时,我正在使用FindFirstFind方法.如您所见,它们都将linq表达式作为输入.我想知道的是,NSubstitute是否允许您在代码中指定要测试的特定表达式.

所以,这是一个服务方法的例子,它说明了我提到的一个Repository方法的使用:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

那么,有没有办法,使用NSubtitute,我可以测试特定的lamda表达式:i => !i.IsProcessed && i.IsConfirmed

任何指导将不胜感激.

Dav*_*pak 14

非常简短的答案是否定的,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));
}
Run Code Online (Sandbox Code Playgroud)

我已将表达式转储到静态Queries类,但您可以使用工厂更好地封装它.因为您具有对所使用的实际表达式的引用,所以您可以设置返回值并检查正常接收的调用.您也可以单独测试表达式.

第二个选项通过使用规范模式进一步采用这一点.假设您将以下成员添加到IRepository接口并引入ISpecification:

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

public interface ISpecification<T> { bool Matches(T item);  }
Run Code Online (Sandbox Code Playgroud)

然后你可以像这样测试它:

//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));
}
Run Code Online (Sandbox Code Playgroud)

同样,您可以单独测试此查询,以确保它符合您的想法.

第三种选择是捕获使用的参数并直接测试它.这有点乱,但有效:

[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 });
}
Run Code Online (Sandbox Code Playgroud)

(这有望在未来的NSubstitute版本中变得更容易)

第四种选择是查找/借用/编写/窃取一些可以比较表达式树的代码,并使用NSubstitute的Arg.Is(...)来获取谓词来比较那里的表达式树.

第五种选择是不对它进行单元测试,只使用真正的InvoiceRepository进行集成测试.而不是担心发生的事情的机制,尝试验证您需要的实际行为.

我的一般建议是准确地查看您需要测试的内容以及如何最好地编写这些测试.请记住,表达式和传递它的事实都需要以某种方式进行测试,并且测试不需要是单元测试.也许值得考虑当前的IRepository界面是否让您的生活更轻松.你可以尝试写你的测试,希望有,然后看你开什么设计出来以支持可测试性.

希望这可以帮助.


Jas*_*ore 5

当我试图弄清楚如何使用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);
}
Run Code Online (Sandbox Code Playgroud)


zol*_*a25 5

有一种方法可以做到这一点,即比较 lambda 表达式是否相等。此处针对相关问题编写了一个非常受欢迎的答案,其中给出了 LambdaCompare 类的示例。

然后,您可以使用此 LambdaCompare 检查模拟设置中的表达式或 lambda 是否相等:

var mockRepository = Substitute.For<IRepository>();
mockRepository.Find(Arg.Is<Expression<Func<Invoice, bool>>>(expr =>
                    LambdaCompare.Eq(expr, i => !i.IsProcessed && i.IsConfirmed))
              .Returns(..etc..)
Run Code Online (Sandbox Code Playgroud)

.Find()仅当使用表达式调用模拟存储库时i => !i.IsProcessed && i.IsConfirmed,它才会返回中指定的内容.Returns()