First the code,
Generic Interface:
public interface IEntityService<TEntity> where TEntity : class
{
IQueryable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "");
Task<TEntity> GetByIDAsync(object id);
Task<TEntity> GetFirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate);
}
Generic Class with interface implementation:
public class EntityService<TEntity> : IEntityService<TEntity> where TEntity : class
{
protected IContext IContext;
protected DbSet<TEntity> IDbSet;
public EntityService(IContext context)
{
IContext = context;
IDbSet = IContext.Set<TEntity>();
}
public virtual IQueryable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
IQueryable<TEntity> query = IDbSet;
if (filter != null)
{
query = query.Where(filter);
}
query = includeProperties.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Aggregate(query, (current, includeProperty) => current.Include(includeProperty));
if (orderBy != null)
{
return orderBy(query);
}
return query;
}
public virtual async Task<TEntity> GetByIDAsync(object id)
{
return await IDbSet.FindAsync(id);
}
public virtual async Task<TEntity> GetFirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate)
{
return await IDbSet.FirstOrDefaultAsync(predicate);
}
}
Specific interface:
public interface ILoginService
{
Task<UserProfileViewModel> GetLoginDetailAsync(string userName);
}
Specific class: Implementing generic class and specific interface
public class LoginService : EntityService<UserAccount>, ILoginService
{
private readonly IContext _iContext;
public LoginService(IContext context): base(context)
{
_iContext = context;
}
async Task<UserProfileViewModel> ILoginService.GetLoginDetailAsync(string userName)
{
var userAcount = await GetFirstOrDefaultAsync(c => c.Username.ToLower() == userName.Trim().ToLower() && c.Active == true);
if (userAcount != null)
{
return Mapper.Map<UserAccount, UserProfileViewModel>(userAcount);
}
return null;
}
}
Now, I am supposed to test LoginService
the one and only method it has
Here's the test code
[Test]
public async Task GetLoginDetailAsync_InvalidUsername_ReturnsNull()
{
var userName = "should not exist!";
var userAccount = new List<UserAccount>()
{
new UserAccount
{
ID = 1,
Name = "Test User"
}
}.AsQueryable();
var mockSet = new Mock<DbSet<UserAccount>>();
var userProfileViewModel = new UserProfileViewModel
{
ID = 1,
Name = Guid.NewGuid().ToString().Substring(0, 8)
};
_context.Setup(c => c.Set<UserAccount>()).Returns(mockSet.Object);
loginService = new LoginService(_context.Object);
mockSet.As<IDbAsyncEnumerable<UserAccount>>().
Setup(m => m.GetAsyncEnumerator()).
Returns(new TestDbAsyncEnumerator<UserAccount>(userAccount.GetEnumerator()));
mockSet.As<IQueryable<UserAccount>>()
.Setup(m => m.Provider)
.Returns(new TestDbAsyncQueryProvider<UserAccount>(userAccount.Provider));
mockSet.As<IQueryable<UserAccount>>().Setup(m => m.Expression).Returns(userAccount.Expression);
mockSet.As<IQueryable<UserAccount>>().Setup(m => m.ElementType).Returns(userAccount.ElementType);
mockSet.As<IQueryable<UserAccount>>().Setup(m => m.GetEnumerator()).Returns(userAccount.GetEnumerator());
var result = await ((ILoginService)loginService).GetLoginDetailAsync(userName);
Assert.IsNull(result);
}
Now, these TestDbAsyncEnumerator
and TestDbAsyncQueryProvider
are taken from msdn to test Async
queries in EF.
The problem
The test throws an exception, that Message: System.NotImplementedException : The member 'IQueryable.Provider' has not been implemented on type 'DbSet1Proxy' which inherits from 'DbSet1'. Test doubles for 'DbSet1' must provide implementations of methods and properties that are used.
Basically, I have not setup the FirstOrDefaultAsync
for mockSet
that is getting called in GetLoginDetailAsync
(it calls to EntityService
, that ends up calling the FirstOrDefaultAsync
of IDbSet
).
I don't know how can I mock that, because the LoginService
doesn't directly inherits it. It inherit's the EntityService
which in turn has that generic method FirstOrDefaultAsync
. I am stuck at how to set up that.
One another thing that I thought was try this
var loginMock = new Mock<LoginService>(_context.Object);
loginMock.As<ILoginService>().Setup(c => c.GetLoginDetailAsync(It.IsAny<string>())).Returns(Task.FromResult<UserProfileViewModel>(null));
loginMock.As<IEntityService<UserAccount>>().Setup(c => c.GetFirstOrDefaultAsync(It.IsAny<Expression<Func<UserAccount, bool>>>())).Returns(Task.FromResult(userAccount.First()));
But I don't think this is the correct way to go, as I would only be testing the mock object. Can anyone suggest me how do I get to setup and test/mock this GetFirstOrDefaultAsync
, or am I totally going in a wrong direction?
UPDATE AFTER ANSWER:
After the answer from @ODawgG, I am updating this. The test worked fine as specified in answer, but now the other test is failing. I wanted to test, if a particular user exits in the system.
Here's the test code: [Test] public async Task Test3() {
var userAccount = new List<UserAccount>()
{
new UserAccount
{
ID = 1,
Username = "User"
}
}.AsQueryable();
var mockSet = new Mock<DbSet<UserAccount>>();
mockSet.As<IDbAsyncEnumerable<UserAccount>>().
Setup(m => m.GetAsyncEnumerator()).
Returns(new TestDbAsyncEnumerator<UserAccount>(userAccount.GetEnumerator()));
mockSet.As<IQueryable<UserAccount>>()
.Setup(m => m.Provider)
.Returns(new TestDbAsyncQueryProvider<UserAccount>(userAccount.Provider));
mockSet.As<IQueryable<UserAccount>>().Setup(m => m.Expression).Returns(userAccount.Expression);
mockSet.As<IQueryable<UserAccount>>().Setup(m => m.ElementType).Returns(userAccount.ElementType);
mockSet.As<IQueryable<UserAccount>>().Setup(m => m.GetEnumerator()).Returns(userAccount.GetEnumerator());
AutoMapConfiguration.Configure();
var entityService = new Mock<IEntityService<UserAccount>>();
entityService
.Setup(service => service.GetFirstOrDefaultAsync(It.IsAny<Expression<Func<UserAccount, bool>>>()))
.ReturnsAsync(
(Expression<Func<UserAccount, bool>> predicate) => userAccount.FirstOrDefault(predicate)
);
var loginService = new LoginService(entityService.Object);
// Act
var result = await ((ILoginService)loginService).GetLoginDetailAsync("User");
// Assert
Assert.IsNotNull(result);
}
This test should pass, as it should query on the userAccount
but it fails, when I was debugging, and it went inside the LoginService
, and I checked _entityService.Get().ToList()
it says 0 count, while it should really say count 1, the userAccount
that I have setup.
Afaik, the IDbSet
is still not setup, and that's why the count is 0, and it's not returning true. How do I setup that? If it is correct, then why is this test failing? Also, I know that moq
isn't really good for testing expression, but I got this predicate
part of code from here.