Advertisement

#25 Mock Entity Framework Async Operations

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.

Defining the problem

  • WebUi
    • Pages
      • Mock-Async.cshtml.cs

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();
        }
    }
}
(a) Starting code to test whether we can use the to list async method on an IQueryable.

Displaying the results

  • WebUi
    • Pages
      • Mock-Async.cshtml

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>
(b) Simple UI that will iterate over our articles.

Again unfortunately for us if we try to take a look at the results we will be greeted by the exception shown in (c).

Invalid Operation Exception thrown due to IQueryable not implementing async methods
(c) Invalid Operation Exception thrown due to IQueryable not implementing async methods.

Enumerator

  • WebUi
    • Features
      • Mocking
        • MockAsyncEnumerator.cs

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());
        }
    }
}
(d) Code need to define our async enumerator.
Advertisement

Query Provider

  • WebUi
    • Features
      • Mocking
        • MockAsyncQueryProvider.cs

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);
        }
    }
}
(e) Code need to define our async query provider.

Enumerable

  • WebUi
    • Features
      • Mocking
        • MockAsyncEnumerable.cs

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);
    }
}
(f) Code need to define our async enumerable using the enumerator and query provider we created previously.

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).

Nuget package information for moq
(g) Moq is a Nuget package that we will be using to mock up a repository that we can then use to serve up our data asynchronously.
  • WebUi
    • Pages
      • Mock-Async.cshtml.cs

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();
        }
    }
}
(h) By using Moq we can easily mock up a DbSet that is capable of using asynchronous methods.
Exciton Interactive LLC
Advertisement