Sometime ago I described how we can use Moq to unit tests elements of DbContext – please check post Mocking DbContext and DbSet with Moq. Unfortunate that post didn’t covered all issues related to that topic. I didn’t write about unit tests asynchronous queries. Today I want to come back to this issue.
We can treat a previous post as a starting point. We already have some code that help us to mock DbSet<T>. And right now we will extend that code to support asynchronous queries. It is very easy. We need only to implement IDbAsyncQueryProvider interface:
public class InMemoryAsyncQueryProvider<TEntity> : IDbAsyncQueryProvider
{
private readonly IQueryProvider innerQueryProvider;
internal InMemoryAsyncQueryProvider(IQueryProvider innerQueryProvider)
{
this.innerQueryProvider = innerQueryProvider;
}
public IQueryable CreateQuery(Expression expression)
{
return new InMemeoryAsyncEnumerable<TEntity>(expression);
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new InMemeoryAsyncEnumerable<TElement>(expression);
}
public object Execute(Expression expression)
{
return this.innerQueryProvider.Execute(expression);
}
public TResult Execute<TResult>(Expression expression)
{
return this.innerQueryProvider.Execute<TResult>(expression);
}
public Task<object> ExecuteAsync(Expression expression, CancellationToken cancellationToken)
{
return Task.FromResult(Execute(expression));
}
public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
{
return Task.FromResult(Execute<TResult>(expression));
}
}
As you probably notice I used in provided implementation two helper classes:
public class InMemeoryAsyncEnumerable<T> : EnumerableQuery<T>, IDbAsyncEnumerable<T>, IQueryable<T>;
{
public InMemeoryAsyncEnumerable(IEnumerable<T> enumerable)
: base(enumerable)
{ }
public InMemeoryAsyncEnumerable(Expression expression)
: base(expression)
{ }
public IDbAsyncEnumerator<T> GetAsyncEnumerator()
{
return new InMemoryDbAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());
}
IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator()
{
return this.GetAsyncEnumerator();
}
IQueryProvider IQueryable.Provider => new InMemoryAsyncQueryProvider<T>(this);
}
public class InMemoryDbAsyncEnumerator<T> : IDbAsyncEnumerator<T>;
{
private readonly IEnumerator<T> innerEnumerator;
public InMemoryDbAsyncEnumerator(IEnumerator<T> enumerator)
{
this.innerEnumerator = enumerator;
}
public void Dispose()
{
this.innerEnumerator.Dispose();
}
public Task<bool> MoveNextAsync(CancellationToken cancellationToken)
{
return Task.FromResult(innerEnumerator.MoveNext());
}
public T Current => this.innerEnumerator.Current;
object IDbAsyncEnumerator.Current => this.Current;
}
And finally we can try our solution in action:
public async Task GetLockedUsers_Invoke_LockedUsers()
{
// Arrange
var fixture = new Fixture();
var lockedUser = fixture.Build<User>().With(u => u.AccountLocked, true).Create();
var users = new List<User>
{
lockedUser,
fixture.Build<User>().With(u => u.AccountLocked, false).Create(),
fixture.Build<User>().With(u => u.AccountLocked, false).Create()
}.AsQueryable();
var usersMock = new Mock<DbSet<User>>();
usersMock.As<IDbAsyncEnumerable<User>>()
.Setup(m => m.GetAsyncEnumerator())
.Returns(new InMemoryDbAsyncEnumerator<User>(users.GetEnumerator()));
usersMock.As<IQueryable<User>>()
.Setup(m => m.Provider)
.Returns(new InMemoryAsyncQueryProvider<User>(users.Provider));
usersMock.As<IQueryable<User>>()
.Setup(m => m.Expression).Returns(users.Expression);
usersMock.As<IQueryable<User>>()
.Setup(m => m.ElementType).Returns(users.ElementType);
usersMock.As<IQueryable<User>>()
.Setup(m => m.GetEnumerator()).Returns(users.GetEnumerator());
var userContextMock = new Mock<UsersContext>();
userContextMock.Setup(x => x.Users).Returns(usersMock.Object);
var usersService = new UsersService(userContextMock.Object);
// Act
var lockedUsers = await usersService.GetLockedUsersAsync();
// Assert
Assert.Equal(new List<User> { lockedUser }, lockedUsers);
}
Of course we should extend the method that has been written previously:
private static Mock<DbSet<T>> CreateDbSetMock<T>(IEnumerable<T> elements) where T : class
{
var elementsAsQueryable = elements.AsQueryable();
var dbSetMock = new Mock<DbSe<T>>();
dbSetMock.As<IDbAsyncEnumerable<T>>()
.Setup(m => m.GetAsyncEnumerator())
.Returns(new InMemoryDbAsyncEnumerator<T>(elementsAsQueryable.GetEnumerator()));
dbSetMock.As<IQueryable<User>>()
.Setup(m => m.Provider)
.Returns(new InMemoryAsyncQueryProvider<T>(elementsAsQueryable.Provider));
dbSetMock.As<IQueryable<T>>()
.Setup(m => m.Expression).Returns(elementsAsQueryable.Expression);
dbSetMock.As<IQueryable<T>>()
.Setup(m => m.ElementType).Returns(elementsAsQueryable.ElementType);
dbSetMock.As<IQueryable<T>>()
.Setup(m => m.GetEnumerator()).Returns(elementsAsQueryable.GetEnumerator());
return dbSetMock;
}
And then our test can be shorten a bit:
[Fact]
public async Task GetLockedUsersAsync_Invoke_LockedUsers()
{
// Arrange
var fixture = new Fixture();
var lockedUser = fixture.Build<User>().With(u => u.AccountLocked, true).Create();
IList<User> users = new List<User>
{
lockedUser,
fixture.Build<User>().With(u => u.AccountLocked, false).Create(),
fixture.Build<User>().With(u => u.AccountLocked, false).Create()
};
var usersMock = CreateDbSetMock(users);
var userContextMock = new Mock<UsersContext>();
userContextMock.Setup(x => x.Users).Returns(usersMock.Object);
var usersService = new UsersService(userContextMock.Object);
// Act
var lockedUsers = await usersService.GetLockedUsersAsync();
// Assert
Assert.Equal(new List<User> {lockedUser}, lockedUsers);
}



Leave A Comment