I'm using XUNIT to test in a dot net core application.
I need to test a service that is internally making an async query on a DbSet in my datacontext.
I've seen here that mocking that DbSet asynchronously is possible.
The problem I'm having is that the IDbAsyncQueryProvider
does not seem to be available in EntityframeworkCore, which I'm using.
Am I incorrect here? Has anyone else got this working?
(Been a long day, hopefully I'm just missing something simple)
EDIT
After asking on GitHub, I got point to this class: https://github.com/aspnet/EntityFramework/blob/dev/src/Microsoft.EntityFrameworkCore/Query/Internal/IAsyncQueryProvider.cs
This is what I've gotten to so far in trying to implement this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Query.Internal;
namespace EFCoreTestQueryProvider
{
internal class TestAsyncQueryProvider<TEntity>: IAsyncQueryProvider
{
private readonly IQueryProvider _inner;
internal TestAsyncQueryProvider(IQueryProvider inner)
{
_inner = inner;
}
IQueryable CreateQuery(Expression expression)
{
return new TestDbAsyncEnumerable<TEntity>(expression);
}
IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new TestDbAsyncEnumerable<TElement>(expression);
}
object Execute(Expression expression)
{
return _inner.Execute(expression);
}
TResult Execute<TResult>(Expression expression)
{
return _inner.Execute<TResult>(expression);
}
IAsyncEnumerable<TResult> ExecuteAsync<TResult>(Expression expression)
{
return Task.FromResult(Execute<TResult>(expression)).ToAsyncEnumerable();
}
Task<TResult> IAsyncQueryProvider.ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
{
return Task.FromResult(Execute<TResult>(expression));
}
}
internal class TestDbAsyncEnumerable<T> : EnumerableQuery<T>, System.Collections.Generic.IAsyncEnumerable<T>, IQueryable<T>
{
public TestDbAsyncEnumerable(IEnumerable<T> enumerable)
: base(enumerable)
{ }
public TestDbAsyncEnumerable(Expression expression)
: base(expression)
{ }
public IAsyncEnumerator<T> GetAsyncEnumerator()
{
return new TestDbAsyncEnumerable<T>(this.AsEnumerable()).ToAsyncEnumerable();
}
IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator()
{
return GetAsyncEnumerator();
}
IAsyncEnumerator<T> IAsyncEnumerable<T>.GetEnumerator()
{
throw new NotImplementedException();
}
IQueryProvider IQueryable.Provider
{
get { return new TestAsyncQueryProvider<T>(this); }
}
}
}
I've now tried to implement this and have run into some more issues, specifically around these two methods:
public IAsyncEnumerator<T> GetAsyncEnumerator()
{
return new TestDbAsyncEnumerable<T>(this.AsEnumerable()).ToAsyncEnumerable();
}
IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator()
{
return GetAsyncEnumerator();
}
I'm hoping that somebody could point me in the right direction as to what I'm doing wrong.
The Entity Framework Core tools help with design-time development tasks. They're primarily used to manage Migrations and to scaffold a DbContext and entity types by reverse engineering the schema of a database.
EF Core 6.0 performance is now 70% faster on the industry-standard TechEmpower Fortunes benchmark, compared to 5.0. This is the full-stack perf improvement, including improvements in the benchmark code, the . NET runtime, etc. EF Core 6.0 itself is 31% faster executing queries.
Microsoft. EntityFrameworkCore. Design contains all the design-time logic for Entity Framework Core. It's the code that all of the various tools (PMC cmdlets like Add-Migration , dotnet ef & ef.exe ) call into. If you don't use Migrations or Reverse Engineering, you don't need it.
EF Core In-Memory Database ProviderThis database provider allows Entity Framework Core to be used with an in-memory database. While some users use the in-memory database for testing, this is generally discouraged; the SQLite provider in in-memory mode is a more appropriate test replacement for relational databases.
I finally got this to work. They slightly changed the interfaces in EntityFrameworkCore from IDbAsyncEnumerable
to IAsyncEnumerable
so the following code worked for me:
public class AsyncEnumerable<T> : EnumerableQuery<T>, IAsyncEnumerable<T>, IQueryable<T>
{
public AsyncEnumerable(Expression expression)
: base(expression) { }
public IAsyncEnumerator<T> GetEnumerator() =>
new AsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());
}
public class AsyncEnumerator<T> : IAsyncEnumerator<T>
{
private readonly IEnumerator<T> enumerator;
public AsyncEnumerator(IEnumerator<T> enumerator) =>
this.enumerator = enumerator ?? throw new ArgumentNullException();
public T Current => enumerator.Current;
public void Dispose() { }
public Task<bool> MoveNext(CancellationToken cancellationToken) =>
Task.FromResult(enumerator.MoveNext());
}
[Fact]
public async Task TestEFCore()
{
var data =
new List<Entity>()
{
new Entity(),
new Entity(),
new Entity()
}.AsQueryable();
var mockDbSet = new Mock<DbSet<Entity>>();
mockDbSet.As<IAsyncEnumerable<Entity>>()
.Setup(d => d.GetEnumerator())
.Returns(new AsyncEnumerator<Entity>(data.GetEnumerator()));
mockDbSet.As<IQueryable<Entity>>().Setup(m => m.Provider).Returns(data.Provider);
mockDbSet.As<IQueryable<Entity>>().Setup(m => m.Expression).Returns(data.Expression);
mockDbSet.As<IQueryable<Entity>>().Setup(m => m.ElementType).Returns(data.ElementType);
mockDbSet.As<IQueryable<Entity>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
var mockCtx = new Mock<SomeDbContext>();
mockCtx.SetupGet(c => c.Entities).Returns(mockDbSet.Object);
var entities = await mockCtx.Object.Entities.ToListAsync();
Assert.NotNull(entities);
Assert.Equal(3, entities.Count());
}
You might be able to clean up those test implementations of the AsyncEnumerable
and AsyncEnumerator
even more. I didn't try, I just got it to work.
Remember your DbSet
on your DbContext
needs to be marked as virtual
or else you will need to implement some interface wrapper over the DbContext
to make this work properly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With