源IQueryable的提供程序未实现IAsyncQueryProvider

时间:2018-06-25 12:12:39

标签: c# unit-testing entity-framework-6 asp.net-core-2.0 iqueryable

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

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。只有实现IEntityQueryProvider的提供程序才能用于Entity Framework异步操作。

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

5 个答案:

答案 0 :(得分:4)

这是因为您的嘲笑方法;您的模拟提供程序只返回panels的{​​{1}},而Query是一个简单的对象,带有LINQ-to-Objects使其可查询:

panels

实际上,这没有实现private readonly IQueryable<Panel> panels = new List<Panel>() { panel }.AsQueryable(); 。如果可以使用 regular 查询提供程序,则应该可以使用伪造的始终同步的版本对其进行欺骗(只需使用IAsyncQueryProvider),但是坦率地说,不确定这是否将是一个有用的测试...此时,您正在跳过return Task.FromResult(Execute(expression))的许多重要现实,以致可能不值得。

答案 1 :(得分:2)

您可以实现一个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));
    }
}

答案 2 :(得分:2)

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

更改实体框架的核心操作,例如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);

答案 3 :(得分:0)

我遇到了同样的问题并以这种方式解决了:

我没有将我的方法 public async Task 切换到 public void,然后使用 .GetAwaiter() 而不是 await 解析异步方法。

答案 4 :(得分:-1)

查询面板时,请先删除默认或异步设置。

还在查询分析时从列表中删除异步