[ACCEPTED]-NSubstitute - Testing for a specific linq expression-nsubstitute
The very short answer is no, NSubstitute 52 doesn't have anything built it to make testing 51 specific expressions easier.
The much longer 50 answer is that there are a few options you 49 can try, and most of them involve avoiding 48 direct use of LINQ in the class under test. I'm 47 not sure if any of these are good ideas 46 as I don't know the full context, but hopefully 45 there will be some info here you can use. In 44 the following examples I've eliminated the 43 Mapper step to make the code samples a bit 42 smaller.
First option is to make it so you 41 can check the expression is the same reference 40 you are expecting, which means you can no 39 longer create it directly in your code under 38 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 37 on a static Queries class, but you could 36 use a factory to encapsulate it better. Because 35 you have an reference to the actual expression 34 used you can set return values and check 33 calls were received as normal. You can also 32 test the expression in isolation.
Second 31 option takes this a bit further by using 30 a specification pattern. Say you add the 29 following member to the IRepository interface 28 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 27 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 26 this query in isolation to make sure it 25 does what you think.
Third option is to catch 24 the argument used and test it directly. This 23 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 22 be getting a bit easier in future NSubstitute 21 versions)
Fourth option would be to find/borrow/write/steal 20 some code that can compare expression trees, and 19 use NSubstitute's Arg.Is(...) that takes 18 a predicate to compare the expression trees 17 there.
Fifth option is to not unit test it 16 to that degree, and just integration test 15 using a real InvoiceRepository. Rather than 14 worry about the mechanics of what's happening, try 13 verifying the actual behaviour you require.
My 12 general advice would be to look at exactly 11 what you need to test and how you can best 10 and most easily write those tests. Remember 9 that both the expression and the fact that 8 it is passed through needs to be tested 7 somehow, and the test need not be a unit 6 test. It may also be worth considering whether 5 the current IRepository interface is making 4 your life easier. You could try writing 3 the tests you would like to have, then see what 2 design you can drive out to support that 1 testability.
Hope this helps.
I stumbled upon this question when I was 7 trying to figure out how to return a specific 6 value using a lambda expression in NSubstitute. However, for 5 my use case I don't care what is actually 4 passed into the linq query, and wanted to 3 share how to return values for linq queries 2 on mocked interfaces in NSubstitute.
So using 1 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 8 lambda expressions for equality. A very 7 popular answer was written for a related 6 question here, which gives an example of a LambdaCompare 5 class.
You can then use this LambdaCompare 4 to check the expression or lambda for equality 3 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 2 .Find()
is called with the expression i => !i.IsProcessed && i.IsConfirmed
, will it 1 return what was specified in .Returns()
I was reluctant to give up on the use of 12 Expression<Func<T,bool>>
in my repository interface, so as an alternative 11 to programming this one particular mock 10 (since NSubstitute didn't support it), I 9 simply created a private class within my 8 test fixture that implemented my repository 7 interface and only the Expression-related 6 method that the test would be using. I was 5 able to continue using NSubstitute to mock 4 all the other dependencies as usual, but 3 I could use this same repository for several 2 different tests and actually get different 1 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();
}
}
}
More Related questions
We use cookies to improve the performance of the site. By staying on our site, you agree to the terms of use of cookies.