源IQueryable的提供程序未实现IAsyncQueryProvider

sin*_*ici 6 c# unit-testing iqueryable entity-framework-6 asp.net-core-2.0

我有一些类似下面的代码,我想编写方法的单元测试。但是我陷入了异步方法。你能帮我吗 ?

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我收到这个错误

源IQueryable的提供程序未实现IAsyncQueryProvider。只有实现IEntityQueryProvider的提供程序可以用于Entity Framework异步操作。

我认为我需要模拟“ FirstOrDefaultAsync”,但不确定,我也不知道该怎么做。我尝试了一些东西,但是无法编译。

Seb*_*ull 6

您可以实现一个AsyncEnumerable可以像这样使用的:

private readonly IQueryable<Panel> panels = new AsyncEnumerable(new List<Panel>() 
{
    panel
});
Run Code Online (Sandbox Code Playgroud)

这是它的实现:

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);
}
Run Code Online (Sandbox Code Playgroud)

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @Tim:请参阅此处了解 Core 3.1 解决方案:/sf/answers/4081987661/ (5认同)
  • 我也试过这个,但它不起作用。它为`IQueryable` 给出了相同的错误。链接:https://msdn.microsoft.com/en-us/library/dn314429(v=vs.113).aspx (2认同)

小智 6

对于实体框架核心中的问题,请使用Moq.EntityFrameworkCore 库并设置您的数据库集,如下所示:

contextMock.Setup(x => x.entity).ReturnsDbSet(entityList);
Run Code Online (Sandbox Code Playgroud)


Mar*_*ell 5

这是因为你的嘲弄方法;您的模拟提供程序只返回panelsfor Query,并且 panels是一个简单的对象,具有 LINQ-to-Objects 将其公开为可查询的:

private readonly IQueryable<Panel> panels = new List<Panel>() { panel }.AsQueryable();
Run Code Online (Sandbox Code Playgroud)

事实上,这并没有实现IAsyncQueryProvider. 如果您可以获得常规查询提供程序,您应该能够用一个虚假的始终同步版本包装它以欺骗它(只需使用return Task.FromResult(Execute(expression))),但坦率地说,我不确定这是否是一个有用的测试......在这一点上,您正在跳过许多重要的现实async,这可能不值得。

  • @TanvirArjel 你能分享答案吗? (5认同)

小智 5

我今天在这个问题上陷入困境,这个lib 完全为我解决了这个问题https://github.com/romantitov/MockQueryable,请参考:

Moking Entity Framework核心操作,例如ToListAsync,FirstOrDefaultAsync等

//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);
Run Code Online (Sandbox Code Playgroud)

  • 对于 EF Core,可以使用 Moq.EntityFrameworkCore 库。 (2认同)