I have some codes like below, I want to write unit tests my method. But I'm stuck in async methods. Can you help me please ?
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); } }
I'm getting this error
The provider for the source IQueryable doesn't implement IAsyncQueryProvider. Only providers that implement IEntityQueryProvider can be used for Entity Framework asynchronous operations.
I think that I need to mock 'FirstOrDefaultAsync' but I'm not sure and I don't know how to do. I tried something, however it couldn't be compiled.
I get stuck on this issue today and this lib resolve it for me https://github.com/romantitov/MockQueryable completely, please refer:
Mocking Entity Framework Core operations such 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);
You could implement an AsyncEnumerable
which can be used like this:
private readonly IQueryable<Panel> panels = new AsyncEnumerable(new List<Panel>() { panel });
Here is the implementation of it:
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); }
The AsyncEnumerator
class:
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()); } }
The AsyncQueryProvider
class:
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)); } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With