I'm trying to create a unit test for a class that calls into an async repository. I'm using ASP.NET Core and Entity Framework Core. My generic repository looks like this.
public class EntityRepository<TEntity> : IEntityRepository<TEntity> where TEntity : class
{
    private readonly SaasDispatcherDbContext _dbContext;
    private readonly DbSet<TEntity> _dbSet;
    public EntityRepository(SaasDispatcherDbContext dbContext)
    {
        _dbContext = dbContext;
        _dbSet = dbContext.Set<TEntity>();
    }
    public virtual IQueryable<TEntity> GetAll()
    {
        return _dbSet;
    }
    public virtual async Task<TEntity> FindByIdAsync(int id)
    {
        return await _dbSet.FindAsync(id);
    }
    public virtual IQueryable<TEntity> FindBy(Expression<Func<TEntity, bool>> predicate)
    {
        return _dbSet.Where(predicate);
    }
    public virtual void Add(TEntity entity)
    {
        _dbSet.Add(entity);
    }
    public virtual void Delete(TEntity entity)
    {
        _dbSet.Remove(entity);
    }
    public virtual void Update(TEntity entity)
    {
        _dbContext.Entry(entity).State = EntityState.Modified;
    }
    public virtual async Task SaveChangesAsync()
    {
        await _dbContext.SaveChangesAsync();
    }
}
Then I have a service class that calls FindBy and FirstOrDefaultAsync on an instance of the repository:
    public async Task<Uri> GetCompanyProductURLAsync(Guid externalCompanyID, string productCode, Guid loginToken)
    {            
        CompanyProductUrl companyProductUrl = await _Repository.FindBy(u => u.Company.ExternalCompanyID == externalCompanyID && u.Product.Code == productCode.Trim()).FirstOrDefaultAsync();
        if (companyProductUrl == null)
        {
            return null;
        }
        var builder = new UriBuilder(companyProductUrl.Url);
        builder.Query = $"-s{loginToken.ToString()}";
        return builder.Uri;
    }
I'm trying to mock the repository call in my test below:
    [Fact]
    public async Task GetCompanyProductURLAsync_ReturnsNullForInvalidCompanyProduct()
    {
        var companyProducts = Enumerable.Empty<CompanyProductUrl>().AsQueryable();
        var mockRepository = new Mock<IEntityRepository<CompanyProductUrl>>();
        mockRepository.Setup(r => r.FindBy(It.IsAny<Expression<Func<CompanyProductUrl, bool>>>())).Returns(companyProducts);
        var service = new CompanyProductService(mockRepository.Object);
        var result = await service.GetCompanyProductURLAsync(Guid.NewGuid(), "wot", Guid.NewGuid());
        Assert.Null(result);
    }
However, when the test executes the call to the repository, I get the following error:
The provider for the source IQueryable doesn't implement IAsyncQueryProvider. Only providers that implement IEntityQueryProvider can be used for Entity Framework asynchronous operations.
How can I properly mock the repository to get this to work?
Thanks to @Nkosi for pointing me to a link with an example of doing the same thing in EF 6: https://msdn.microsoft.com/en-us/library/dn314429.aspx. This didn't work exactly as-is with EF Core, but I was able to start with it and make modifications to get it working. Below are the test classes that I created to "mock" IAsyncQueryProvider:
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());
    }
}
And here is my updated test case that uses these classes:
[Fact]
public async Task GetCompanyProductURLAsync_ReturnsNullForInvalidCompanyProduct()
{
    var companyProducts = Enumerable.Empty<CompanyProductUrl>().AsQueryable();
    var mockSet = new Mock<DbSet<CompanyProductUrl>>();
    mockSet.As<IAsyncEnumerable<CompanyProductUrl>>()
        .Setup(m => m.GetEnumerator())
        .Returns(new TestAsyncEnumerator<CompanyProductUrl>(companyProducts.GetEnumerator()));
    mockSet.As<IQueryable<CompanyProductUrl>>()
        .Setup(m => m.Provider)
        .Returns(new TestAsyncQueryProvider<CompanyProductUrl>(companyProducts.Provider));
    mockSet.As<IQueryable<CompanyProductUrl>>().Setup(m => m.Expression).Returns(companyProducts.Expression);
    mockSet.As<IQueryable<CompanyProductUrl>>().Setup(m => m.ElementType).Returns(companyProducts.ElementType);
    mockSet.As<IQueryable<CompanyProductUrl>>().Setup(m => m.GetEnumerator()).Returns(() => companyProducts.GetEnumerator());
    var contextOptions = new DbContextOptions<SaasDispatcherDbContext>();
    var mockContext = new Mock<SaasDispatcherDbContext>(contextOptions);
    mockContext.Setup(c => c.Set<CompanyProductUrl>()).Returns(mockSet.Object);
    var entityRepository = new EntityRepository<CompanyProductUrl>(mockContext.Object);
    var service = new CompanyProductService(entityRepository);
    var result = await service.GetCompanyProductURLAsync(Guid.NewGuid(), "wot", Guid.NewGuid());
    Assert.Null(result);
}
                        Try to use my Moq/NSubstitute/FakeItEasy extension MockQueryable: supported all Sync/Async operations (see more examples here)
//1 - create a List<T> with test items
var users = new List<UserEntity>()
{
 new UserEntity,
 ...
};
//2 - build mock by extension
var mock = users.AsQueryable().BuildMock();
//3 - setup the mock as Queryable for Moq
_userRepository.Setup(x => x.GetQueryable()).Returns(mock.Object);
//3 - setup the mock as Queryable for NSubstitute
_userRepository.GetQueryable().Returns(mock);
DbSet also supported
//2 - build mock by extension
var mock = users.AsQueryable().BuildMockDbSet();
//3 - setup DbSet for Moq
var userRepository = new TestDbSetRepository(mock.Object);
//3 - setup DbSet for NSubstitute
var userRepository = new TestDbSetRepository(mock);
Notes:
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