Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
360 views
in Technique[技术] by (71.8m points)

c# - NSubstitute - Testing for a specific linq expression

I am using the repository pattern in an MVC 3 application I am currently developing. My repository interface looks as follows:

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);
}

In a lot of instances, when coding methods in my service classes, I am using the FindFirst and Find methods. As you can see, they both take a linq expression as input. What I am wanting to know is whether there is a way NSubstitute allows you to specify the particular expression you want to test for in your code.

So, here is an example of a service method that illustrates the use of one of the Repository methods I have mentioned:

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);
    }
}

So, is there a way, using NSubtitute, that I can test for the specific lamda expression: i => !i.IsProcessed && i.IsConfirmed ?

Any guidance will be appreciated.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The very short answer is no, NSubstitute doesn't have anything built it to make testing specific expressions easier.

The much longer answer is that there are a few options you can try, and most of them involve avoiding direct use of LINQ in the class under test. I'm not sure if any of these are good ideas as I don't know the full context, but hopefully there will be some info here you can use. In the following examples I've eliminated the Mapper step to make the code samples a bit smaller.

First option is to make it so you can check the expression is the same reference you are expecting, which means you can no longer create it directly in your code under test. For example:

//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));
}

I've dumped the expression on a static Queries class, but you could use a factory to encapsulate it better. Because you have an reference to the actual expression used you can set return values and check calls were received as normal. You can also test the expression in isolation.

Second option takes this a bit further by using a specification pattern. Say you add the following member to the IRepository interface and introduce an ISpecification:

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

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

You can then test it like this:

//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));
}

Again, you can test this query in isolation to make sure it does what you think.

Third option is to catch the argument used and test it directly. This is a bit messy but works:

[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 });
}

(This will hopefully be getting a bit easier in future NSubstitute versions)

Fourth option would be to find/borrow/write/steal some code that can compare expression trees, and use NSubstitute's Arg.Is(...) that takes a predicate to compare the expression trees there.

Fifth option is to not unit test it to that degree, and just integration test using a real InvoiceRepository. Rather than worry about the mechanics of what's happening, try verifying the actual behaviour you require.

My general advice would be to look at exactly what you need to test and how you can best and most easily write those tests. Remember that both the expression and the fact that it is passed through needs to be tested somehow, and the test need not be a unit test. It may also be worth considering whether the current IRepository interface is making your life easier. You could try writing the tests you would like to have, then see what design you can drive out to support that testability.

Hope this helps.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...