我有一些像下面的代码,我想写单元测试我的方法。但我陷入了异步方法。你能帮我吗 ?
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',但我不确定,我不知道该怎么做。我试了一下,但无法编译。
这是因为你的嘲弄方法;你的模拟提供者只为panels
返回Query
,而panels
是一个简单的对象,LINQ-to-Objects将其公开为可查询:
private readonly IQueryable<Panel> panels = new List<Panel>() { panel }.AsQueryable();
实际上,这并没有实现IAsyncQueryProvider
。如果你能掌握常规查询提供程序,你应该能够使用假的始终同步版本来包装它(只需使用return Task.FromResult(Execute(expression))
),但坦率地说,我不确定这是一个有用的测试。那时你正在跳过async
的许多重要现实,它可能不值得。
你可以实现一个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));
}
}
我今天卡在这个问题上,这个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);
查询面板时,删除firstordefault中的异步。
在查询分析时,还要从tolist中删除异步