#25 Mock Entity Framework Async Operations
Sunday, April 7, 2019
There are many times that we would like to serve data from an in memory collection, whether it be for unit testing or just messing around to see how something works, where we have to/want to do it asynchronously. Entity Framework comes with a handy extension method to convert a collection to a list asynchronously that we can use for this purpose. Unfortunately for us there are a few hoops that we must jump through to do this and we will see what those are in this article. By the time we are done here we will have created all of the helper classes that we need to mock up a database set that is capable of utilizing asynchronous methods.
Parts
- Part 29: Offset Pager Urls
- Part 28: Offset Pager Start
- Part 27: Mock Context Builder
- Part 26: Mock Repository
- Part 25: Mock Async
- Part 24: Picture Tag Helper
- Part 23: Img DPR Tag Helper
- Part 22: Img Responsive Tag Helper
- Part 21: Img Optimized Display
- Part 20: Img Optimization
- Part 19: Img Lazy Loading
- Part 18: Img Responsive
- Part 17: Bottom Nav
- Part 16: Main Nav Cookie
- Part 15: Main Nav Mobile
- Part 14: Main Nav Search
- Part 13: Main Nav Auth
- Part 12: Main Nav Anchors
- Part 11: Main Nav Logo
- Part 10: Search Results
- Part 9: Search Manager
- Part 8: Search Start
- Part 7: Seeding the Database
- Part 6: Domain Database
- Part 5: Emailing Exceptions
- Part 4: Mailkit
- Part 3: View Renderer
- Part 2: Upgrade to 2.1
- Part 1: Quick Start
Defining the problem
- WebUi
- Pages
- Mock-Async.cshtml.cs
- Pages
We are going to start by naively attempting to to use the Entity Framework
ToListAsync
method that we invoke on
a list the we have already used the AsQueryable
extension method on (a).
Mock-Async.cshtml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using WebUi.Domain;
namespace WebUi.Pages
{
public class MockAsyncModel : PageModel
{
private readonly IQueryable<Article> _articles;
public MockAsyncModel()
{
var articles = new List<Article>();
for (var i = 1; i <= 20; i++)
{
articles.Add(new Article
{
Author = "Christopher R. Jamell",
Created = DateTime.UtcNow,
Id = i,
Title = $"Title {i}"
});
}
_articles = articles.AsQueryable();
}
public List<Article> Articles { get; private set; }
public async Task OnGet()
{
Articles = await _articles.ToListAsync();
}
}
}
Displaying the results
- WebUi
- Pages
- Mock-Async.cshtml
- Pages
For the purpose of this article we will just iterate over our articles to see that everything is working as it should be (b).
Mock-Async.cshtml
@page
@model MockAsyncModel
@{
ViewData["Title"] = "Mock Async";
}
<div class="articles">
@foreach (var article in Model.Articles)
{
<div class="article">
<div class="title">@article.Title</div>
</div>
}
</div>
Again unfortunately for us if we try to take a look at the results we will be greeted by the exception shown in (c).
Enumerator
- WebUi
- Features
- Mocking
- MockAsyncEnumerator.cs
- Mocking
- Features
As I mentioned previously we are going to have to create a few helper classes and we will start with defining an async enumerator (d).
MockAsyncEnumerator.cs
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace WebUi.Features.Mocking
{
internal class MockAsyncEnumerator<T> : IAsyncEnumerator<T>
{
private readonly IEnumerator<T> _enumerator;
public MockAsyncEnumerator(IEnumerator<T> enumerator)
{
_enumerator = enumerator;
}
public T Current => _enumerator.Current;
public void Dispose()
{
_enumerator.Dispose();
}
public Task<bool> MoveNext(CancellationToken cancellationToken)
{
return Task.FromResult(_enumerator.MoveNext());
}
}
}
Query Provider
- WebUi
- Features
- Mocking
- MockAsyncQueryProvider.cs
- Mocking
- Features
Next up we need to define an async query provider (e).
MockAsyncQueryProvider.cs
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Query.Internal;
namespace WebUi.Features.Mocking
{
internal class MockAsyncQueryProvider<TEntity> : IAsyncQueryProvider
{
private readonly IQueryProvider _provider;
internal MockAsyncQueryProvider(IQueryProvider provider)
{
_provider = provider;
}
public IQueryable CreateQuery(Expression expression)
{
return new MockAsyncEnumerable<TEntity>(expression);
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new MockAsyncEnumerable<TElement>(expression);
}
public object Execute(Expression expression)
{
return _provider.Execute(expression);
}
public TResult Execute<TResult>(Expression expression)
{
return _provider.Execute<TResult>(expression);
}
public IAsyncEnumerable<TResult> ExecuteAsync<TResult>(Expression expression)
{
return new MockAsyncEnumerable<TResult>(expression);
}
public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
{
return Task.FromResult(Execute<TResult>(expression));
}
IAsyncEnumerable<TResult> IAsyncQueryProvider.ExecuteAsync<TResult>(Expression expression)
{
return new MockAsyncEnumerable<TResult>(expression);
}
}
}
Enumerable
- WebUi
- Features
- Mocking
- MockAsyncEnumerable.cs
- Mocking
- Features
Now that have the last two classes defined we are now in a position to create our async enumerable (f).
MockAsyncEnumerable.cs
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace WebUi.Features.Mocking
{
internal class MockAsyncEnumerable<T> : EnumerableQuery<T>, IAsyncEnumerable<T>, IQueryable<T>
{
public MockAsyncEnumerable(Expression expression)
: base(expression)
{ }
public IAsyncEnumerator<T> GetEnumerator()
{
return new MockAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());
}
IQueryProvider IQueryable.Provider => new MockAsyncQueryProvider<T>(this);
}
}
Time to mock
To make use of our new mocking capability we are first going to need to download the Moq package from Nuget. The version of the package at the time of the writing of this article is shown in (g).
- WebUi
- Pages
- Mock-Async.cshtml.cs
- Pages
Now that Moq has been installed we are now able to create our mock DbSet
that is capable of using asynchronous methods (h).
Mock-Async.cshtml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using Moq;
using WebUi.Domain;
using WebUi.Features.Mocking;
namespace WebUi.Pages
{
public class MockAsyncModel : PageModel
{
private readonly DbSet<Article> _articles;
public MockAsyncModel()
{
...
var data = articles.AsQueryable();
var dbSetMock = new Mock<DbSet<Article>>();
dbSetMock.As<IAsyncEnumerable<Article>>()
.Setup(x => x.GetEnumerator())
.Returns(new MockAsyncEnumerator<Article>(data.GetEnumerator()));
dbSetMock.As<IQueryable<Article>>()
.Setup(x => x.Provider)
.Returns(new MockAsyncQueryProvider<Article>(data.Provider));
dbSetMock.As<IQueryable<Article>>()
.Setup(x => x.Expression)
.Returns(data.Expression);
dbSetMock.As<IQueryable<Article>>()
.Setup(x => x.ElementType)
.Returns(data.ElementType);
dbSetMock.As<IQueryable<Article>>()
.Setup(x => x.GetEnumerator())
.Returns(() => data.GetEnumerator());
_articles = dbSetMock.Object;
}
...
public async Task OnGet()
{
Articles = await _articles.ToListAsync();
}
}
}