IQueryable<T> is a tricky interface. Some people argue that it is actually a bad abstraction, because it is impossible to implement it. This post is not about trying to prove or disprove this point. People who say this do have a point, but I do think they are wrong. Aside from that, it is a fact that query providers don't usually implement all the LInQ operators: just think of Entity Framework and the overload of Contains that takes a character parameter.

This is also why some people say that you shouldn't return IQueryable<T> from your repositories. Just imagine that I use an ORM that actually does support Contains() with the character overload. So the repository returns an IQueryable, I append this to it, and then call ToList() and everything fine. But then I later decide that I want to move to EF 6. I can easily make the change since the project uses the repository and unit of work patterns, so I simply supply a different implementation right? Wrong. Now every query that used the character-based Contains() breaks at the ToList() call. That's actually a good reason to use the specification pattern with the repository pattern. This is (would be) actually the violation of the Liskov-substitution principle.

But I have to say, I disagree with this reasoning: I think these people don't really get what an IQueryable<T> is. And since I disagree, I don't always use the specification pattern in these systems. And in these situations, I do find it problematic to create proper mocks for some queries, especially since the asnychronous ones.

Async evalution on mocks

If you have ever tried to mock your data access code with in-memory lists or hashsets, I think you know what I mean. The problem is that if you wrap your list into an IQueryable<T> then you can call ToListAsync() on it — but it gives you an exception. Just take this little piece of code as an example:

var myList = new List<string> { "Akos1", "Bkos2", "Akos3" };
var inMemoryQueryable = myList.AsQueryable();
var result = await inMemoryQueryable.Where(m => m.StartsWith("A")).ToListAsync();

This compiles, but at runtime throws a very nasty exception:

System.InvalidOperationException: 'The source IQueryable doesn't implement IDbAsyncEnumerable<System.String>. Only sources that implement IDbAsyncEnumerable can be used for Entity Framework asynchronous operations. 

So if you want to mock your repositories with lists, you are out of luck. As the exception says, you need a type that implements IDbAsyncEnumerable<T>.

Implementing an async db enumerable

So this is usually the part where I show you how I decided to implement this interface, then how I looked at the documentation, the source code of EF 6 and then came up with a very cool solution. Well, not this time :)

I did take a look at the EF 6 repository on Github, and it turns out that Microsoft has met this problem as well and they have alreay implemented test doubles to support in-memory mocking of async queries. Cool :) Of course these types are not available in the EF 6 library, but you can go to the Github repo and check out the test doubles. They are very, very cool. There is one to support async evaluation on in-memory structures, but there's also a memory based implementation of IDbSet.

Whenever I have to support a mock for my repositories that return IQueryable<T>, I usually just copy this code into the project (almost) and then I can finally use these types as the basis for my mocks. I just have to wrap the collection into an InMemoryAsyncQueryable<T> and I'm good to go:

List<string> myList = new List<string> { "Akos1", "Bkos2", "Akos3" };
var inMemoryQueryable = new InMemoryAsyncQueryable<string>(myList);
var result = await inMemoryQueryable.Where(m => m.StartsWith("A")).ToListAsync();     

I did add some modifications to the original code, but nothing major; needless to say, all credit goes to Microsoft. I did create a Github repo just for these files with a little sample, check it out if you are interested.

Supporting async LInQ evaluation on IQueryable mocks
Share this

© 2019. All Rights Reserved.

Falconium theme based on Ghostium Theme

Proudly published with Ghost