Wednesday, May 20, 2020

The provider for the source IQueryable doesn't implement IAsyncQueryProvider (Mocking asynchronous database calls in .Net entity Framwork with MOQ Framework)

Whenever we like to develop using test-driven development approach, Unit Testing is always essential. When it comes to .Net Core and .Net Entity Framework it little bit different, no matter what unit testing framework we use, we need to mock libraries.

Assuming we have .Net core solution with test project, all we need to add the Moq from NuGet.
Install-Package Moq

Issue:
One of the issue which I encountered while testing, the asynchronous call to the database was throwing the following error. 

System.InvalidOperationException: The provider for the source IQueryable doesn't implement IDbAsyncQueryProvider. Only providers that implement IDbAsyncQueryProvider can be used for Entity Framework asynchronous operations. For more details see http://go.microsoft.com/fwlink/?LinkId=287068.

Here is my fucntino that I wanted to test: 

public async Task<string> GetTrainingTypeDescription(string code)
        {
            var trainingType = await _dbContext.TrainingType.Where(x => x.TrainingTypeCode.Equals(code)).FirstOrDefaultAsync();

            if (trainingType != null)
                return trainingType.Descriptions.ToString();

            return string.Empty;
        }

I wanted to test above function with in-memory set of data, the test failed because our test data won’t support the interfaces needed to make the asynchronous call. The reason behind this failure because the traditional provider for IQuerable and IEnumerable doesn't implement the IAsyncQueryProvider interface needed for the entity framework asynchronous extension method. We need to create the supporting classes that will help us to mock the asynchronous calls.

internal class TestAsyncEnumerable<T> : EnumerableQuery, IAsyncEnumerable, IQueryable
    {
        public TestAsyncEnumerable(IEnumerable enumerable)
            : base(enumerable)
        { }

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

        public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken)
        {
            return GetEnumerator();
        }

        public IAsyncEnumerator GetEnumerator()
        {
            return new TestAsyncEnumerator(this.AsEnumerable().GetEnumerator());
        }

        IQueryProvider IQueryable.Provider
        {
            get { return new TestAsyncQueryProvider(this); }
        }
    }


internal class TestAsyncEnumerator<T> : IAsyncEnumerator
    {
        private readonly IEnumerator _inner;

        public TestAsyncEnumerator(IEnumerator inner)
        {
            _inner = inner;
        }

        public ValueTask DisposeAsync()
        {
            _inner.Dispose();
            return new ValueTask();
        }

        public T Current => _inner.Current;

        public ValueTask<bool> MoveNextAsync()
        {
            return new ValueTask<bool>(_inner.MoveNext());
        }
    }


internal class TestAsyncQueryProvider<TEntity> : IAsyncQueryProvider
    {
        private readonly IQueryProvider _inner;

        internal TestAsyncQueryProvider(IQueryProvider inner)
        {
            _inner = inner;
        }

        public IQueryable CreateQuery(Expression expression)
        {
            return new TestAsyncEnumerable(expression);
        }

        public IQueryable CreateQuery<TElement>(Expression expression)
        {
            return new TestAsyncEnumerable(expression);
        }

        public object Execute(Expression expression)
        {
            return _inner.Execute(expression);
        }

        public TResult Execute<TResult>(Expression expression)
        {
            return _inner.Execute(expression);
        }

        public IAsyncEnumerable ExecuteAsync<TResult>(Expression expression)
        {
            return new TestAsyncEnumerable(expression);
        }

        public TResult ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
        {
             return _inner.Execute(expression);
        }
    }


Now that we have created our supported classes. We need to make sure when we return mock version of the DbSet containing the data we pass as the TestData parameter. The resulting DbSet will have support to asynchronous calls because it will implement our custom classes.

To do that we need to create following helping class.

public class UnitTestHelper
    {
        public static Mock> GetQueryableMockDbSet<T>(IQueryable testData) where T : class
        {
            var mockSet = new Mock>();

            mockSet.As>()
                .Setup(x => x.GetAsyncEnumerator(default))
                .Returns(new TestAsyncEnumerator(testData.GetEnumerator()));

            mockSet.As>()
                .Setup(x => x.Provider)
                .Returns(new TestAsyncQueryProvider(testData.Provider));

            mockSet.As>()
                .Setup(x => x.Expression)
                .Returns(testData.Expression);

            mockSet.As>()
                .Setup(x => x.ElementType)
                .Returns(testData.ElementType);

            mockSet.As>()
                .Setup(x => x.GetEnumerator())
                .Returns(testData.GetEnumerator());

            mockSet.As>()
                .Setup(x => x.GetEnumerator())
                .Returns(testData.GetEnumerator());

            return mockSet;
        }
    }

 Next Step is to create the data:
private static IQueryable GetTrainingTypes()
        {
            return new List
            {
                new TrainingType()
                {
                    TrainingTypeCode = "Qa",
                    Descriptions = " Quality Assurance Training",
                    Name =  "QA Training"
                },
                new TrainingType()
                {
                    TrainingTypeCode = "Lab",
                    Descriptions = "Laboratory Training.",
                    Name =  "Labs Training."
                }
            }.AsQueryable();
        }

Now that we have the test data and the helper classes needed for that data to be accessed asynchronously by Entity Framework, Lets write our test function.

So given our function GetTrainingTypeDescription(), we want to test it to make sure it works and it’s returning the correct value.

[Fact]
public async void When_querying_for_a_TrainingTypeDescription__returns_Description()
        {
            var context = new Mock();
            var trainingtype = GetTrainingTypes();
            var cancellationToken = new CancellationToken();

            context.SetupGet(c => c.TrainingType)
                .Returns(UnitTestHelper.GetQueryableMockDbSet( trainingtype).Object);

            // Call the function to test
    var typeHandler = new TypeHandler(context.Object);
    var result = await TypeHandler.GetTrainingTypeDescription("Lab");

            Assert.True(result.HasValue);
        }

Now, Run the Test. It will be successfully completed without errors. You can debug and see how it works.

No comments: