44

I have some codes like below, I want to write unit tests my method. But I'm stuck in async methods. Can you help me please ?

public class Panel
{
    public int Id { get; set; }
    [Required] public double Latitude { get; set; }
    public double Longitude { get; set; }
    [Required] public string Serial { get; set; }
    public string Brand { get; set; }
}

public class CrossSolarDbContext : DbContext
{
    public CrossSolarDbContext()
    {
    }

    public CrossSolarDbContext(DbContextOptions<CrossSolarDbContext> options) : base(options)
    {
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
    }
}

public interface IGenericRepository<T>
{
    Task<T> GetAsync(string id);

    IQueryable<T> Query();

    Task InsertAsync(T entity);

    Task UpdateAsync(T entity);
}

public abstract class GenericRepository<T> : IGenericRepository<T>
    where T : class, new()
{
    protected CrossSolarDbContext _dbContext { get; set; }

    public async Task<T> GetAsync(string id)
    {
        return await _dbContext.FindAsync<T>(id);
    }

    public IQueryable<T> Query()
    {
        return _dbContext.Set<T>().AsQueryable();
    } 

    public async Task InsertAsync(T entity)
    {
        _dbContext.Set<T>().Add(entity);
        await _dbContext.SaveChangesAsync();
    }

    public async Task UpdateAsync(T entity)
    {
        _dbContext.Entry(entity).State = EntityState.Modified;
        await _dbContext.SaveChangesAsync();
    }
}

public interface IPanelRepository : IGenericRepository<Panel> { }

public class PanelRepository : GenericRepository<Panel>, IPanelRepository
{
    public PanelRepository(CrossSolarDbContext dbContext)
    {
        _dbContext = dbContext;
    }
}

[Route("[controller]")]
public class PanelController : Controller
{
    private readonly IPanelRepository _panelRepository;

    public PanelController(IPanelRepository panelRepository)
    {
        _panelRepository = panelRepository;
    }

    // GET panel/XXXX1111YYYY2222
    [HttpGet("{panelId}")]
    public async Task<IActionResult> Get([FromRoute] string panelId)
    {
        Panel panel = await _panelRepository.Query().FirstOrDefaultAsync(x => x.Serial.Equals(panelId, StringComparison.CurrentCultureIgnoreCase));
        if (panel == null) return NotFound();
        return Ok(panel);
    }
}

public class PanelControllerTests
{
    private readonly PanelController _panelController;
    private static readonly Panel panel = new Panel { Id = 1, Brand = "Areva", Latitude = 12.345678, Longitude = 98.7655432, Serial = "AAAA1111BBBB2222" };

    private readonly IQueryable<Panel> panels = new List<Panel>() { panel }.AsQueryable();
    private readonly Mock<IPanelRepository> _panelRepositoryMock = new Mock<IPanelRepository>();

    public PanelControllerTests()
    {
        _panelRepositoryMock.Setup(x => x.Query()).Returns(panels);
        // I also tried this. I got another error 'Invalid setup on an extension method: x => x.FirstOrDefaultAsync<Panel>(It.IsAny<Expression<Func<Panel, Boolean>>>(), CancellationToken)'
        // _panelRepositoryMock.As<IQueryable<Panel>>().Setup(x => x.FirstOrDefaultAsync(It.IsAny<Expression<Func<Panel, bool>>>(), default(CancellationToken))).ReturnsAsync(panel);
        _panelController = new PanelController(_panelRepositoryMock.Object);
    }

    [Fact]
    public async Task Register_ShouldInsertOneHourElectricity()
    {
        IActionResult result = await _panelController.Get("AAAA1111BBBB2222");
        Assert.NotNull(result);
        var createdResult = result as CreatedResult;
        Assert.NotNull(createdResult);
        Assert.Equal(201, createdResult.StatusCode);
    }
}

I'm getting this error

The provider for the source IQueryable doesn't implement IAsyncQueryProvider. Only providers that implement IEntityQueryProvider can be used for Entity Framework asynchronous operations.

I think that I need to mock 'FirstOrDefaultAsync' but I'm not sure and I don't know how to do. I tried something, however it couldn't be compiled.

Sinan AKYAZICI
  • 3,942
  • 5
  • 35
  • 60
  • 3
    note really an answer, but an unrelated tip: if the *only* thing `async` in a method is an `await` on the last line, you can almost always remove the `async` modifier from the method and just `return` the downstream task - i.e. `return _dbContext.SaveChangesAsync();` (not `await`) - this removes a level of `Task` indirection and avoids an async state machine in your method. – Marc Gravell Jun 25 '18 at 12:27
  • @MarcGravell Thank you very much for your attention. Actually `Get` method is not so short like this. It contains another operations as well. I did it short for better understanding of the main purpose. Sorry for causing confusion. – Sinan AKYAZICI Jun 25 '18 at 13:58
  • You're better off doing integration tests with controller actions. Controllers require so much set up and mocking to function correctly via a unit test, that it pretty much invalidates the unit test. Does it fail because it's actually failing or because you didn't mock something right? On the flip side, it might actually pass with your mocks but fail when run within a real request pipeline, because again, you might not have mocked everything just right. Instead, use the test host: https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-2.1 – Chris Pratt Jun 25 '18 at 14:24
  • @MarcGravell I actually avoid omitting the async because it can break Exception handling. – Tim Pohlmann Apr 15 '20 at 14:21

6 Answers6

47

I get stuck on this issue today and this lib resolve it for me https://github.com/romantitov/MockQueryable completely, please refer:

Mocking Entity Framework Core operations such ToListAsync, FirstOrDefaultAsync etc.

//1 - create a List<T> with test items
var users = new List<UserEntity>()
{
  new UserEntity{LastName = "ExistLastName", DateOfBirth = DateTime.Parse("01/20/2012")},
  ...
};

//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);
Mafii
  • 7,227
  • 1
  • 35
  • 55
Do Tat Hoan
  • 901
  • 9
  • 9
11

You could implement an AsyncEnumerable which can be used like this:

private readonly IQueryable<Panel> panels = new AsyncEnumerable(new List<Panel>() 
{
    panel
});

Here is the implementation of it:

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

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

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

    IQueryProvider IQueryable.Provider => new AsyncQueryProvider<T>(this);
}

The AsyncEnumerator class:

public class AsyncEnumerator<T> : IAsyncEnumerator<T>
{
    private readonly IEnumerator<T> _inner;

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

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

    public T Current => _inner.Current;

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

The AsyncQueryProvider class:

public class AsyncQueryProvider<TEntity> : IAsyncQueryProvider
{
    private readonly IQueryProvider _inner;

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

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

    public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
    {
        return new AsyncEnumerable<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 AsyncEnumerable<TResult>(expression);
    }

    public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
    {
        return Task.FromResult(Execute<TResult>(expression));
    }
}
Sebastian Krogull
  • 1,468
  • 1
  • 15
  • 31
  • 2
    I have tried this too, but it doesn't work. It gives the same error for `IQueryable`. Link: https://msdn.microsoft.com/en-us/library/dn314429(v=vs.113).aspx – Sinan AKYAZICI Jun 25 '18 at 14:22
  • It does work in EF Core 2.2 when return empty IQueryable and ToListAsync – Kuroro Nov 19 '19 at 06:03
  • 1
    This doesn't work with EF Core 3.1.3, I assume the interfaces have changed (for example `IAsyncQueryProvider` has XML documentation on it warning that it's intended as an internal API and might change without notice). I haven't looked into when the interfaces changed, though I'd guess it's the same for all versions of EF Core 3. – Tim Jun 10 '20 at 10:14
  • @Tim yeah Im struggling with the same issue at the mo. Would be nice if the answer can be updated to support EF Core 3.1.x – GoldenAge May 27 '21 at 13:25
  • 4
    @Tim: See here for a Core 3.1 solution: https://stackoverflow.com/a/58314109/98422 – Gary McGill Jul 16 '21 at 08:44
  • @GoldenAge I assume you mean the repo in the answer linked by Gary McGill? I've updated the link there. The repo still exists, it's just that the file which was originally linked no longer exists. The code is still in the repo, it's just been tweaked a bit and moved around. – Tim Jul 26 '21 at 11:27
  • Thanks @Tim, I think I can accept this as a workaround. – GoldenAge Jul 26 '21 at 15:08
  • Here's implementation for .net core 3.1 and higher https://stackoverflow.com/a/71076807/4905704 – Leonid Pavlov Feb 11 '22 at 08:04
7

This is because of your mocking approach; your mock provider just returns panels for Query, and panels is a simple object with LINQ-to-Objects exposing it as queryable:

private readonly IQueryable<Panel> panels = new List<Panel>() { panel }.AsQueryable();

Indeed, this does not implement IAsyncQueryProvider. If you can get hold of the regular query provider, you should be able to wrap that with a fake always-synchronous version to spoof it (just use return Task.FromResult(Execute(expression))), but frankly I'm not sure that this would be a useful test... at that point you're skipping so many of the important realities of async that it probably isn't worth it.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • I am facing the similar problem with similar code. How it can be fixed now? – TanvirArjel Dec 12 '18 at 14:52
  • @TanvirArjel by preferring integration tests to unit tests – Marc Gravell Dec 13 '18 at 10:44
  • Gravel,Thank you! I have already solved this issue perfectly :) with the help of a answer to the another question. – TanvirArjel Dec 13 '18 at 10:47
  • 5
    @TanvirArjel Can you share the answer? – fingers10 Jul 15 '19 at 07:18
  • @TanvirArjel bump – GoldenAge May 27 '21 at 13:15
  • @MarcGravell I'd say now when the EF Core 3.1 is available there would be a huge benefit of having the possibility to test async LINQ queries just in C#. The result in C# should be exactly the same as the one after query transformation to SQL. It's just faster to unit testing something, integration tests are very expensive and require a proper setup which often is not something that can be achieved very quickly and easily... – GoldenAge May 27 '21 at 13:28
  • 1
    @GoldenAge I think I'm going to keep leaning towards integration testing here; unit testing *where the mock is this complicated*: isn't very useful IMO; maybe invest more in the "proper setup" - I've never found it a barrier – Marc Gravell May 27 '21 at 13:31
6

For issue in entity framework core ,use Moq.EntityFrameworkCore library and setup your dbset as below:

contextMock.Setup(x => x.entity).ReturnsDbSet(entityList);
Peter Csala
  • 17,736
  • 16
  • 35
  • 75
Ahmed
  • 61
  • 1
  • 2
-1

I was facing the same issue and solved it that way:

Instead of having my method public async Task I switched it to public void and then resolved the asynchronous methods with .GetAwaiter() instead of await.

Muhammad Dyas Yaskur
  • 6,914
  • 10
  • 48
  • 73
Georgi Popov
  • 43
  • 2
  • 9
-7

When querying for panel, removing the async in firstordefault.

Also remove the async from tolist when querying for analytics

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459