2016-11-07 14 views
13

Próbuję utworzyć test jednostki dla klasy, która wywołuje asynchroniczne repozytorium. Używam ASP.NET Core i Entity Framework Core. Moje ogólne repozytorium wygląda tak.Jak wyśmiać repozytorium asynchroniczne z 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(); 
    } 
} 

Następnie mam klasy usług, który wywołuje FindBy i FirstOrDefaultAsync na wystąpienie repozytorium:

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; 
    } 

Próbuję mock repozytorium połączenia w moim teście poniżej:

[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); 
    } 

Jednak, gdy test wykonuje wywołanie do repozytorium, pojawia się następujący błąd:

The provider for the source IQueryable doesn't implement IAsyncQueryProvider. Only providers that implement IEntityQueryProvider can be used for Entity Framework asynchronous operations. 

Jak mogę właściwie kpić z repozytorium, aby to działało?

+2

Może być to może pomóc https://msdn.microsoft.com/en-us/library/dn314429.aspx – Nkosi

+0

Przeczytaj sekcję "Testowanie zapytań asynchronicznych" – Nkosi

+0

Musisz sfałszować zarówno interfejsy 'IQueryable ' jak i 'IAsyncEnumerableAccessor ' – Dealdiane

Odpowiedz

23

Dzięki @Nkosi za wskazanie mi linku z przykładem zrobienia tego samego w EF 6: https://msdn.microsoft.com/en-us/library/dn314429.aspx. To nie działa dokładnie tak, jak w przypadku EF Core, ale mogłem zacząć od niego i wprowadzać modyfikacje, aby działało. Poniżej znajdują się zajęcia testowe że stworzone do „mock” 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()); 
    } 
} 

I tu jest mój zaktualizowany przypadek testowy, który używa tych klas:

[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); 
} 

Dziękuję bardzo za pomoc!

+0

Cieszę się, że w końcu to wymyśliłeś. Szukałem kodu źródłowego na github, żeby sprawdzić, czy mają jakieś przykłady. Co zabawne, badałem jedną z nich, kiedy sprawdzałem i zobaczyłem, że przyszedłeś do swojego własnego rozwiązania. Chłodny. – Nkosi

+0

Chciałbym przekonwertować to na metodę rozszerzenia, dzięki czemu można go ponownie użyć w testach. – Nkosi

+1

Sprawdź odpowiedź podaną [tutaj] (http://stackoverflow.com/a/40500030/5233410), która odwołuje się do tej odpowiedzi, gdzie użyto metody rozszerzenia. Szczęśliwe kodowanie !!! – Nkosi

0

Staraj się używać rozszerzenia Min MockQueryable: https://github.com/romantitov/MockQueryable obsługiwane wszystkie operacje Sync/Async

//1 - create a List<T> with test items 
var users = new List<UserEntity>() 
{ 
new UserEntity, 
... 
}; 

//2 - build mock by extension 
var mock = users.AsQueryable().BuildMock(); 

//3 - setup the mock as Queryable 
_userRepository.Setup(x => x.GetQueryable()).Returns(mock.Object); 
Powiązane problemy