How to Mock an Entity Framework 6 Async Projecting Query

entity-framework entity-framework-6 mocking


I've been able to generate a lot of tests that pass by using the section Using async queries for testing of the MSDN article Using a Mocking Framework for Testing.

Here is my test code, which uses the mocks NSubstitute:

var dummyQueryable = locations.AsQueryable();

var mock = Substitute.For<DbSet<Location>, IDbAsyncEnumerable<Location>, IQueryable<Location>>();
((IDbAsyncEnumerable<Location>)mock).GetAsyncEnumerator().Returns(new TestDbAsyncEnumerator<Location>(dummyQueryable.GetEnumerator()));
((IQueryable<Location>)mock).Provider.Returns(new TestDbAsyncQueryProvider<Location>(dummyQueryable.Provider));
sut.DataContext.Locations = mock;

var result = await sut.Index();


sut.Index() does little but results in the following question:

await DataContext.Locations
    .GroupBy(l => l.Area)

This is effective up until I insert a projection into the formula:

await DataContext.Locations
    .GroupBy(l => l.Area)
    .Select(l => new LocationsIndexVM{ Area = l.Key }) // added projection

It leads to the following exception:

The source IQueryable doesn't implement IDbAsyncEnumerable<LocationsIndexVM>. Only sources that implement IDbAsyncEnumerable can be used for Entity Framework asynchronous operations. For more details see
   at System.Data.Entity.QueryableExtensions.AsDbAsyncEnumerable(IQueryable`1 source)
   at System.Data.Entity.QueryableExtensions.ToListAsync(IQueryable`1 source)
   at Example.Web.Controllers.HomeController.<Index>d__0.MoveNext() in HomeController.cs: line 25
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Example.Test.Web.Controllers.HomeControllerShould.<TempTest>d__4.MoveNext() in HomeControllerShould.cs: line 71

I have a uploaded a quick and easy fix that reproduces this issue.

Can somebody give an example of the steps necessary to unit test a query that is both simple and complex?async and includes a.Select() projection?

4/2/2014 8:46:16 PM

Accepted Answer

I dug a little and discovered that the problem is with the way theTestDbAsyncEnumerable<T> reveals theIQueryProvider . Below are my best assumptions for the reasoning and the answer.

TestDbAsyncEnumerable<T> receives fromEnumerableQuery<T> , who in turn descended fromIQueryable<T> , and specifically carries out theProvider a feature of this interface

IQueryProvider IQueryable.Provider { get ... }

I'm assuming that the LINQ internals explicitly cast a type before attempting to access the value because it is officially implemented.Provider :


The type binding rules for explicit implementations, in my opinion—which I don't have a source for because I don't want to bother looking for one—are different from those for implicit implementations.Provider real estate on yourTestDbAsyncEnumerable<T> is not seen as an application ofIQueryable<T>.Provider when there is an explicit one higher up the chain, yourTestDbAsyncQueryProvider<T> is never given back.

Making this correction will help.TestDbAsyncEnumerable<T> also pass onIQueryable<T> and clearly put into practice theProvider The following property (modified from the the MSDN article you referenced):

public class TestDbAsyncEnumerable<T> : EnumerableQuery<T>, IDbAsyncEnumerable<T>, IQueryable<T>
    public TestDbAsyncEnumerable(IEnumerable<T> enumerable) : base(enumerable)
    { }

    public TestDbAsyncEnumerable(Expression expression) : base(expression)
    { }

    public IDbAsyncEnumerator<T> GetAsyncEnumerator()
        return new TestDbAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());

    IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator()
        return GetAsyncEnumerator();

    IQueryProvider IQueryable.Provider
        get { return new TestDbAsyncQueryProvider<T>(this); }
4/3/2014 3:06:20 AM

Related Questions


Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow