开发者

NSubstitute - Testing for a specific linq expression

I am using the repository pattern in an MVC 3 applicati开发者_StackOverflow社区on 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.


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.


I stumbled upon this question when I was trying to figure out how to return a specific value using a lambda expression in NSubstitute. However, for my use case I don't care what is actually passed into the linq query, and wanted to share how to return values for linq queries on mocked interfaces in NSubstitute.

So using the example from above

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


There is a way to do this, by comparing lambda expressions for equality. A very popular answer was written for a related question here, which gives an example of a LambdaCompare class.

You can then use this LambdaCompare to check the expression or lambda for equality in your mock setup:

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

Only if the mock repository .Find() is called with the expression i => !i.IsProcessed && i.IsConfirmed, will it return what was specified in .Returns()


I was reluctant to give up on the use of Expression<Func<T,bool>> in my repository interface, so as an alternative to programming this one particular mock (since NSubstitute didn't support it), I simply created a private class within my test fixture that implemented my repository interface and only the Expression-related method that the test would be using. I was able to continue using NSubstitute to mock all the other dependencies as usual, but I could use this same repository for several different tests and actually get different results from different inputs.

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜