web-dev-qa-db-ja.com

ソースIQueryableのプロバイダーはIAsyncQueryProviderを実装していません

私は以下のようなコードをいくつか持っています、私のメソッドをユニットテストで書きたいです。しかし、私は非同期メソッドにこだわっています。助けてくれませんか?

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

このエラーが発生しています

ソースIQueryableのプロバイダーはIAsyncQueryProviderを実装しません。 Entity Frameworkの非同期操作には、IEntityQueryProviderを実装するプロバイダーのみを使用できます。

「FirstOrDefaultAsync」をモックする必要があると思いますが、確信が持てず、方法がわかりません。試してみましたが、コンパイルできませんでした。

6
sinanakyazici

次のように使用できるAsyncEnumerableを実装できます。

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

以下にその実装を示します。

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

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

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

今日この問題で立ち往生し、このライブラリは私のためにそれを解決します https://github.com/romantitov/MockQueryable 完全に、参照してください:

ToListAsync、FirstOrDefaultAsyncなどのMoking Entity Framework Core操作

//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

これはあなたのあざけるアプローチのためです。モックプロバイダーはpanelsに対してQueryを返すだけで、panelsはLINQ-to-Objectsがクエリ可能として公開している単純なオブジェクトです。

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

実際、これはIAsyncQueryProviderを実装していません。 regularクエリプロバイダーを取得できる場合、偽の常時同期バージョンでそれをラップしてスプーフィングすることができます(return Task.FromResult(Execute(expression))を使用します)が、率直に言って「これが有用なテストになるかどうかわからない...その時点では、asyncの重要な現実の多くをスキップしているので、おそらく価値がないだろう。

4
Marc Gravell