NSubstituting IQueryable<> Where() Method

1,565 views
Skip to first unread message

naushervan

unread,
Apr 8, 2015, 5:02:27 PM4/8/15
to nsubs...@googlegroups.com
Hi all,

First of all, I love the friendly syntax of NSubstitute! Great library!

I seem to be facing a problem. I need to test the following code:

var user = userRepository.Where(o => o.Email == model.Email).FirstOrDefault();
if (user != null) { // Some code }
else { //Some code }

The repository variable above is an instance of

public interface IUserRepository : IRepositoryMongo<User>
{
// Some methods other than Where
}

public interface IRepositoryMongo<T> : IQueryable<T> where T : class
{
// Some methods other than Where
}

So the Where method is inherited from IQueryable.

When I use NSubstitute to set up a mock Where method:

var temp = new List<User>();
temp.Add(new User
{
    Email = "a...@gmail.com"
});
userRepository = Substitute.For<IUserRepository>();
userRepository.Where(user => true).ReturnsForAnyArgs(temp.AsQueryable());

An ArgumentNullException results on executing the last line above with message: "Value cannot be null. Parameter name: arguments"

Here is the stack trace:
   at System.Linq.Expressions.Expression.RequiresCanRead(Expression expression, String paramName)
   at System.Linq.Expressions.Expression.ValidateOneArgument(MethodBase method, ExpressionType nodeKind, Expression arg, ParameterInfo pi)
   at System.Linq.Expressions.Expression.ValidateArgumentTypes(MethodBase method, ExpressionType nodeKind, ReadOnlyCollection`1& arguments)
   at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, IEnumerable`1 arguments)
   at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, Expression[] arguments)
   at System.Linq.Queryable.Where[TSource](IQueryable`1 source, Expression`1 predicate)
   at XXXX.Service.UnitTest.Controllers.UserControllerTest.UpdateMobileUserTest() in e:\...\Controllers\UserControllerTest.cs:line 692

The declaration for Where is
public static System.Linq.IQueryable<TSource> Where<TSource>(this System.Linq.IQueryable<TSource> source, System.Linq.Expressions.Expression<Func<TSource,bool>> predicate)

where TSource should be User in above case.

What could be the problem here? Can we not create a mock method for "Where" or any method which requires a lambda expression using NSubstitute? Am I using NSubstitute wrong? What can I do to mock the following line of project code so I can test the if and else clauses?
var user = userRepository.Where(o => o.Email == model.Email).FirstOrDefault();

Thanks a lot,
Naushervan

David Tchepak

unread,
Apr 9, 2015, 5:57:36 PM4/9/15
to nsubs...@googlegroups.com
Hi Naushervan,

`Where` can not be mocked by NSubstitute because it is a static extension method.

If you are just using the methods on `IQueryable<T>`, can you modify the code you are testing to accept an `IQueryable<User>` instead of an `IUserRepository`?  Then I think you could pass in the `test` list directly and avoid mocking at all.

Regards,
David



--
You received this message because you are subscribed to the Google Groups "NSubstitute" group.
To unsubscribe from this group and stop receiving emails from it, send an email to nsubstitute...@googlegroups.com.
To post to this group, send email to nsubs...@googlegroups.com.
Visit this group at http://groups.google.com/group/nsubstitute.
For more options, visit https://groups.google.com/d/optout.

naushervan

unread,
Apr 10, 2015, 6:06:30 AM4/10/15
to nsubs...@googlegroups.com
Thanks a lot, David, for clearing this up!

Forgive my asking, but do you think that other libraries like Moq will also be unable to mock such code? I do not have access (/permission) to the code I am supposed to Unit Test, and this is a chance for me to learn what can and cannot be mocked. What I'm taking out from here is that extension methods just cannot be mocked at all, or should I take that this is a limitation of NSubstitute?

Thanks for the help,
Naushervan

David Tchepak

unread,
Apr 10, 2015, 7:02:46 AM4/10/15
to nsubs...@googlegroups.com
Moq, FakeItEasy, RhinoMocks and NSubstitute all use Castle DynamicProxy for generating mocks, so I think they all have similar limitations. Basically, you can use one of these libraries with any type you could manually override by sub-typing. e.g. replacing virtual methods in a sub-class, or by providing a new implementation of an interface. This means sealed, static and non-virtual members of classes can not be mocked using this approach (because we can't change the behaviour of these members). There are other non-DynamicProxy based approaches that can mock a lot more -- for example, Microsoft Fakes and TypeMock Isolator.

There are different techniques for working with types that can not be easily mocked, requiring varying amounts of changes to the existing code. Things like static members or extension methods can delegate to an instance that can be mocked. Or code can be wrapped with an adapter that can be mocked. Or better yet, see if mocking can be avoided and stick with plain old unit tests/assertions. :)

For me, I found the easiest way to learn what mocking libraries can and can't do was to ignore them entirely and focus on manually creating mocks first. This means every time you want a mock, you manually have to create the sub-type and add hooks to configure their behaviour. You may never need anything more than this, especially if you only use mocks as a last-resort. I found this really useful for helping me to learn about testing, and made the jump to mocking libraries (particularly the DynamicProxy-based ones) much easier. Focus on how to test the code first, then look for tools to make that process easier. 

Hope this helps. :)
Regards,
David

naushervan

unread,
Apr 10, 2015, 7:22:46 AM4/10/15
to nsubs...@googlegroups.com
Thanks, David, for the detailed clarification and good guidance. :)

Best Regards,
Naushervan

Charlie Poole

unread,
Apr 10, 2015, 12:46:52 PM4/10/15
to nsubs...@googlegroups.com
Hi David,

That's the most succinct description of how to work with mocking
libraries that I've ever read. Plus, I agree with it!

Charlie
Reply all
Reply to author
Forward
0 new messages