我正在尝试为调用异步存储库的类创建一个单元测试。我正在使用ASP.NET Core和Entity Framework Core。我的通用存储库如下所示。
public class EntityRepository<TEntity> : IEntityRepository<TEntity> where TEntity : class { private readonly SaasDispatcherDbContext _dbContext; private readonly DbSet<TEntity> _dbSet; public EntityRepository(SaasDispatcherDbContext dbContext) { _dbContext = dbContext; _dbSet = dbContext.Set<TEntity>(); } public virtual IQueryable<TEntity> GetAll() { return _dbSet; } public virtual async Task<TEntity> FindByIdAsync(int id) { return await _dbSet.FindAsync(id); } public virtual IQueryable<TEntity> FindBy(Expression<Func<TEntity, bool>> predicate) { return _dbSet.Where(predicate); } public virtual void Add(TEntity entity) { _dbSet.Add(entity); } public virtual void Delete(TEntity entity) { _dbSet.Remove(entity); } public virtual void Update(TEntity entity) { _dbContext.Entry(entity).State = EntityState.Modified; } public virtual async Task SaveChangesAsync() { await _dbContext.SaveChangesAsync(); } }
然后,我有一个服务类,在存储库的一个实例上调用FindBy和FirstOrDefaultAsync:
public async Task<Uri> GetCompanyProductURLAsync(Guid externalCompanyID, string productCode, Guid loginToken) { CompanyProductUrl companyProductUrl = await _Repository.FindBy(u => u.Company.ExternalCompanyID == externalCompanyID && u.Product.Code == productCode.Trim()).FirstOrDefaultAsync(); if (companyProductUrl == null) { return null; } var builder = new UriBuilder(companyProductUrl.Url); builder.Query = $"-s{loginToken.ToString()}"; return builder.Uri; }
我正在尝试在下面的测试中模拟存储库调用:
[Fact] public async Task GetCompanyProductURLAsync_ReturnsNullForInvalidCompanyProduct() { var companyProducts = Enumerable.Empty<CompanyProductUrl>().AsQueryable(); var mockRepository = new Mock<IEntityRepository<CompanyProductUrl>>(); mockRepository.Setup(r => r.FindBy(It.IsAny<Expression<Func<CompanyProductUrl, bool>>>())).Returns(companyProducts); var service = new CompanyProductService(mockRepository.Object); var result = await service.GetCompanyProductURLAsync(Guid.NewGuid(), "wot", Guid.NewGuid()); Assert.Null(result); }
但是,当测试执行对存储库的调用时,出现以下错误:
The provider for the source IQueryable doesn't implement IAsyncQueryProvider. Only providers that implement IEntityQueryProvider can be used for Entity Framework asynchronous operations.
如何正确模拟存储库以使其正常工作?
感谢@Nkosi为我指向的链接提供了在EF 6中执行相同操作的示例:https ://msdn.microsoft.com/zh-cn/library/dn314429.aspx 。这与EF Core并不完全一样,但是我能够从它开始并进行修改以使其正常工作。下面是我创建的用于“模拟” IAsyncQueryProvider的测试类:
internal class TestAsyncQueryProvider<TEntity> : IAsyncQueryProvider { private readonly IQueryProvider _inner; internal TestAsyncQueryProvider(IQueryProvider inner) { _inner = inner; } public IQueryable CreateQuery(Expression expression) { return new TestAsyncEnumerable<TEntity>(expression); } public IQueryable<TElement> CreateQuery<TElement>(Expression expression) { return new TestAsyncEnumerable<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 TestAsyncEnumerable<TResult>(expression); } public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken) { return Task.FromResult(Execute<TResult>(expression)); } } internal class TestAsyncEnumerable<T> : EnumerableQuery<T>, IAsyncEnumerable<T>, IQueryable<T> { public TestAsyncEnumerable(IEnumerable<T> enumerable) : base(enumerable) { } public TestAsyncEnumerable(Expression expression) : base(expression) { } public IAsyncEnumerator<T> GetEnumerator() { return new TestAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator()); } IQueryProvider IQueryable.Provider { get { return new TestAsyncQueryProvider<T>(this); } } } internal class TestAsyncEnumerator<T> : IAsyncEnumerator<T> { private readonly IEnumerator<T> _inner; public TestAsyncEnumerator(IEnumerator<T> inner) { _inner = inner; } public void Dispose() { _inner.Dispose(); } public T Current { get { return _inner.Current; } } public Task<bool> MoveNext(CancellationToken cancellationToken) { return Task.FromResult(_inner.MoveNext()); } }
这是我使用这些类的更新的测试用例:
[Fact] public async Task GetCompanyProductURLAsync_ReturnsNullForInvalidCompanyProduct() { var companyProducts = Enumerable.Empty<CompanyProductUrl>().AsQueryable(); var mockSet = new Mock<DbSet<CompanyProductUrl>>(); mockSet.As<IAsyncEnumerable<CompanyProductUrl>>() .Setup(m => m.GetEnumerator()) .Returns(new TestAsyncEnumerator<CompanyProductUrl>(companyProducts.GetEnumerator())); mockSet.As<IQueryable<CompanyProductUrl>>() .Setup(m => m.Provider) .Returns(new TestAsyncQueryProvider<CompanyProductUrl>(companyProducts.Provider)); mockSet.As<IQueryable<CompanyProductUrl>>().Setup(m => m.Expression).Returns(companyProducts.Expression); mockSet.As<IQueryable<CompanyProductUrl>>().Setup(m => m.ElementType).Returns(companyProducts.ElementType); mockSet.As<IQueryable<CompanyProductUrl>>().Setup(m => m.GetEnumerator()).Returns(() => companyProducts.GetEnumerator()); var contextOptions = new DbContextOptions<SaasDispatcherDbContext>(); var mockContext = new Mock<SaasDispatcherDbContext>(contextOptions); mockContext.Setup(c => c.Set<CompanyProductUrl>()).Returns(mockSet.Object); var entityRepository = new EntityRepository<CompanyProductUrl>(mockContext.Object); var service = new CompanyProductService(entityRepository); var result = await service.GetCompanyProductURLAsync(Guid.NewGuid(), "wot", Guid.NewGuid()); Assert.Null(result); }