0

I need to write the UT for async method, I am using EF6. Then I have the error: "the source 'iqueryable' doesn't implement 'iasyncenumerable moq".

I search on stackoverflow and found that link How to mock an async repository with Entity Framework Core

But I dont understand the code bellow.

Can anyone help me to understand the code below. Thanks

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

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

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

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

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

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

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

    public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken     cancellationToken)
    {
        return Task.FromResult(Execute<TResult>(expression));
    }
}
internal class TestAsyncEnumerable<T> : EnumerableQuery<T>, IAsyncEnumerable<T>, IQueryable<T>
{
    public TestAsyncEnumerable(IEnumerable<T> enumerable)
        : base(enumerable)
    { }

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

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

    IQueryProvider IQueryable.Provider
    {
        get { return new TestAsyncQueryProvider<T>(this); }
    }
}
internal class TestAsyncEnumerator<T> : IAsyncEnumerator<T>
{
    private readonly IEnumerator<T> _inner;

    public TestAsyncEnumerator(IEnumerator<T> inner)
    {
        _inner = inner;
    }

    public void Dispose()
    {
        _inner.Dispose();
    }

    public T Current
    {
        get
        {
            return _inner.Current;
        }
    }

    public Task<bool> MoveNext(CancellationToken cancellationToken)
    {
        return Task.FromResult(_inner.MoveNext());
    }
}

write UT for async method in EF6.

Joris Schellekens
  • 8,483
  • 2
  • 23
  • 54
  • The error appears to be in code that you didn't post here. Unless maybe you're trying to call one of these methods using "Moq" as a generic type. – Robert Harvey Jun 20 '23 at 13:25
  • @RobertHarvey I just post my code. can you guide me how to the do changes to run my test . thanks – Peter Nguyen Jun 22 '23 at 05:29

1 Answers1

0

I'm not sure about your specific error message, but to explain the code: When using a mocking framework to substitute something like a repository or a DbSet where the consumer is accessing an IQueryable, your mock will need to return a known set of data.

For instance if I have a repository with a method like:

public IQueryable<Order> GetPendingOrders(DateTime? startDate = null);

and I want to mock that I might have a mock like:

var testOrders = new []
{
   new Order { /* fill in order details... /*},
   new Order { /* fill in order details... /*}
};

var mockRepository = new Mock<OrderRepository>();
mockRepository.Setup(x => x.GetPendingOrders(It.IsAny<DateTime>()))
    .Returns(testOrders.AsQueryable());

... and code under test calling that mocked repository then performing a synchronous call like:

var orders = OrderRepository.GetPendingOrders(startDate).ToList();

... will work just fine. Where it will fail is if the code under test expects to use an asynchronous operation.

var orders = await OrderRepository.GetPendingOrders(startDate).ToListAsync();

Using the mock that passes an Array or a List<Order> will fail as the asynchronous EF operation is expecting something that works as an AsyncEnumerable and EnumerableQuery, leading to that code example you employed.

I'd recently come across a problem where the similar code I had used was failing with Autofac's ProjectTo method, even with a synchronous operation. The culprit was this method:

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

The updated example I found looks more like:

public IQueryable CreateQuery(Expression expression)
{
    switch (expression)
    {
        case MethodCallExpression m:
            {
                var resultType = m.Method.ReturnType; // it should be IQueryable<T>
                var tElement = resultType.GetGenericArguments()[0];
                var queryType = typeof(AsyncQueryable<>).MakeGenericType(tElement);
                return (IQueryable)Activator.CreateInstance(queryType, expression);
            }
        default:
            return new TestAsyncEnumerable<TEntity>(expression);
    }
}

The other variance I found from your code and the example I had employed was your code extends IAsyncEnumerable<TEntity> where mine for EF6 extends IDbAsyncEnumerable<TEntity>. The implementation details are otherwise pretty much identical but that may likely be significant when dealing with asynchronous operations via EF as it may be expecting a class that is recognized as IDbAsyncEnumerable, not just IAsyncEnumerable.

Steve Py
  • 26,149
  • 3
  • 25
  • 43