web-dev-qa-db-fra.com

Le fournisseur de la source IQueryable n'implémente pas IAsyncQueryProvider

J'ai quelques codes comme ci-dessous, je veux écrire des tests unitaires ma méthode. Mais je suis coincé dans les méthodes asynchrones. Pouvez-vous m'aider s'il vous plaît ?

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);
    }
}

Je reçois cette erreur

Le fournisseur de la source IQueryable n'implémente pas IAsyncQueryProvider. Seuls les fournisseurs qui implémentent IEntityQueryProvider peuvent être utilisés pour les opérations asynchrones Entity Framework.

Je pense que je dois me moquer de 'FirstOrDefaultAsync' mais je ne suis pas sûr et je ne sais pas comment faire. J'ai essayé quelque chose, mais il n'a pas pu être compilé.

6
sinanakyazici

Vous pouvez implémenter un AsyncEnumerable qui peut être utilisé comme ceci:

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

En voici l'implémentation:

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);
}

La classe AsyncEnumerator:

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());
    }
}

La classe AsyncQueryProvider:

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));
    }
}
6
Sebastian Krogull

Je suis bloqué sur ce problème aujourd'hui et cette bibliothèque le résout pour moi https://github.com/romantitov/MockQueryable complètement, veuillez vous référer:

Moking Entity Framework Core opérations telles que 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);
5
Do Tat Hoan

C'est à cause de votre approche moqueuse; votre fournisseur simulé renvoie simplement panels pour Query, et panels est un simple objet avec LINQ-to-Objects l'exposant comme interrogeable:

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

En effet, cela n'implémente pas IAsyncQueryProvider. Si vous pouvez mettre la main sur le fournisseur de requêtes normal, vous devriez être capable d'envelopper avec une fausse version toujours synchrone pour l'usurper (utilisez simplement return Task.FromResult(Execute(expression))), mais franchement, je Je ne suis pas sûr que ce serait un test utile ... à ce stade, vous sautez tellement de réalités importantes de async que cela n'en vaut probablement pas la peine.

4
Marc Gravell