rgvlee/EntityFrameworkCore.Testing

Mocked DbSet does not implement IAsyncQueryProvider

Closed this issue · 3 comments

When using the package to mock the underlying dbcontext for repositories and using a query with a structure like dbset.AsQueryable().Where(...).SingleOrDefaultAsync(new CancellationToken())

I get a System.InvalidOperationException:
The provider for the source IQueryable doesn't implement IAsyncQueryProvider. Only providers that implement IAsyncQueryProvider can be used for Entity Framework asynchronous operations.

The mocked dataset should implement both IQueryable and IAsyncEnumerableAccessor interfaces. See discussion: https://stackoverflow.com/questions/40476233/how-to-mock-an-async-repository-with-entity-framework-core
And see package https://github.com/romantitov/MockQueryable

Hey Mischa, the provider that gets returned from certain operations does implement IAsyncQueryProvider, and the db set provider itself is a mocked IAsyncQueryProvider; I suspect the problem here is the usage of AsQueryable() on the set itself before performing the LINQ operations.

A key difference between this library and something like MockQueryable is that this library uses the Microsoft in-memory provider to provide a lot of the DbContext operations - the mocked parts are those not supported by the in-memory provider. Invoking AsQueryable() on a set I suspect returns an in-memory provider sequence that doesn't include the IAsyncQueryProvider that this library creates for mocked operations. It'll really depend on what AsQueryable() does internally; I'll take a look.

MockQueryable as I understand creates a sequence which implements those interfaces, so at creation it has control over what the sequence (and the provider) looks like which is why it may work for this particular case.

Is there any reason you are invoking AsQueryable() on an DbSet<>? As it should already implement IQueryable<>.

I've attempted to repo the issue but can't get it to break as yet on both my unstaged code and the current releases for both EFCore2 (1.1.0) and EFCore 3 (2.2.0) versions.

The following passes which indicates that AsQueryable() is returning a queryable with the mocked query provider.

[Test]
public void AsQueryable_Set_ReturnsIQueryableOfTWithMockedQueryProvider()
{
    var mockedDbContext = Create.MockedDbContextFor<TestDbContext>();
    
    var mockedSetAsQueryable = mockedDbContext.TestEntities.AsQueryable();

    var asyncProvider = mockedSetAsQueryable.Provider as IAsyncQueryProvider;
    Assert.That(asyncProvider, Is.Not.Null);
}

Scaled up to the same invocation which is failing for you (AsQueryable().Where(expression).SingleOrDefaultAsync()), tests on both single or default outcomes pass:

[Test]
public async Task AsQueryableThenWhereThenSingleOrDefaultAsync_WhereOperationReturnsTrue_ReturnsSingleEntity()
{
    var fixture = new Fixture();
    var entities = fixture.CreateMany<TestEntity>().ToList();
    var entityToFind = entities.ElementAt(1);
    var mockedDbContext = Create.MockedDbContextFor<TestDbContext>();
    var mockedSet = mockedDbContext.TestEntities;
    mockedSet.AddRange(entities);
    mockedDbContext.SaveChanges();

    var result = await mockedSet.AsQueryable().Where(x => x.Guid.Equals(entityToFind.Guid)).SingleOrDefaultAsync();

    Assert.That(result, Is.EqualTo(entityToFind));
}

[Test]
public async Task AsQueryableThenWhereThenSingleOrDefaultAsync_WhereOperationReturnsFalse_ReturnsDefault()
{
    var fixture = new Fixture();
    var entities = fixture.CreateMany<TestEntity>().ToList();
    var mockedDbContext = Create.MockedDbContextFor<TestDbContext>();
    var mockedSet = mockedDbContext.TestEntities;
    mockedSet.AddRange(entities);
    mockedDbContext.SaveChanges();

    var result = await mockedSet.AsQueryable().Where(x => x.Guid.Equals(Guid.NewGuid())).SingleOrDefaultAsync();

    Assert.That(result, Is.Null);
}

Admittedly for the EFCore 3 version I am testing with EFCore 3.1.1 so there could be an issue there. Any chance you can provide more code, what version you are using for EntityFrameworkCore, EntityFrameworkCore.Testing, whether you are using the Moq or NSubstitute version, what version of Moq or NSubstitute you are using, and so on to repo the problem?

With successful tests of the reported case, I've closed this issue for now. Happy to reopen if more detail becomes available.