首先是代码,
通用接口:
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);
}
与接口实现的通用类:
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);
}
}
特定接口:
public interface ILoginService
{
Task<UserProfileViewModel> GetLoginDetailAsync(string userName);
}
特定类:实现通用类和特定接口
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;
}
}
现在,我应该测试LoginService
它具有
这是测试代码
[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);
}
现在,这些TestDbAsyncEnumerator
和TestDbAsyncQueryProvider
从MSDN中获取以测试EF中的Async
查询。
问题
测试引发了一个例外,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.
基本上,我没有为mockSet
设置FirstOrDefaultAsync
,该CC_6在GetLoginDetailAsync
中被调用(它调用EntityService
,最终调用IDbSet
的CC_11)。
我不知道该如何模拟,因为LoginService
没有直接继承它。它继承了EntityService
,又具有该通用方法FirstOrDefaultAsync
。我被困在如何设置。
我认为是尝试这个
的另一件事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()));
,但我认为这不是正确的方法,因为我只会测试模拟对象。谁能建议我如何进行设置和测试/嘲笑此GetFirstOrDefaultAsync
,或者我完全朝着错误的方向?
答案后更新:
@odawgg的答案后,我正在更新此信息。该测试正如答案中指定的那样正常工作,但是现在另一个测试失败了。我想测试,如果特定用户在系统中退出。
这是测试代码: [测试] 公共异步任务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);
}
该测试应该通过,因为它应该在userAccount
上查询,但是当我调试时失败,并且它进入LoginService
,并且我检查了_entityService.Get().ToList()
,它说0计数,虽然它应该真正说计数1,我设置的userAccount
。afaik,IDbSet
仍未设置,这就是为什么计数为0,并且返回不正确。我该如何设置?如果是正确的,那么为什么该测试失败了?另外,我知道moq
对于测试表达并不好,但是我从这里获得了代码的一部分。
我同意@fabio。无需从EntityService<T>
继承,而是将其注入LogService
类。
重构您的班级看起来如下:
public class LoginService : ILoginService
{
private readonly IEntityService<UserAccount> _entityService;
public LoginService(IEntityService<UserAccount> entityService)
{
_entityService = entityService;
}
async Task<UserProfileViewModel> ILoginService.GetLoginDetailAsync(string userName)
{
var userAcount = await _entityService.GetFirstOrDefaultAsync(c => c.Username.ToLower() == userName.Trim().ToLower() && c.Active);
if (userAcount != null)
{
return Mapper.Map<UserAccount, UserProfileViewModel>(userAcount);
}
return null;
}
}
您的测试看起来像这样:
[Test]
public async Task GetLoginDetailAsync_InvalidUsername_ReturnsNull()
{
// Arrange
MapperInitialize.Configure();
var entityService = new Mock<IEntityService<UserAccount>>();
entityService
.Setup(service => service.GetFirstOrDefaultAsync(It.IsAny<Expression<Func<UserAccount, bool>>>()))
.ReturnsAsync(new UserAccount
{
ID = 1,
Name = "Test User"
});
var loginService = new LoginService(entityService.Object);
// Act
var result = await ((ILoginService)loginService).GetLoginDetailAsync(It.IsAny<string>());
// Assert
Assert.IsNotNull(result);
}
这是更新的测试,包括测试表达式:
[Test]
public async Task GetLoginDetailAsync_InvalidUsername_ReturnsNull()
{
// Arrange
MapperInitialize.Configure();
var entityService = new Mock<IEntityService<UserAccount>>();
var userAccount = new UserAccount
{
ID = 1,
Username = "Test User",
Active = true
};
var expressionResult = false;
entityService
.Setup(service => service.GetFirstOrDefaultAsync(It.IsAny<Expression<Func<UserAccount, bool>>>()))
.Callback<Expression<Func<UserAccount, bool>>>(expression =>
{
expressionResult = expression.Compile().Invoke(userAccount);
})
.ReturnsAsync(userAccount);
var loginService = new LoginService(entityService.Object);
// Act
var result = await ((ILoginService)loginService).GetLoginDetailAsync("Test User");
// Assert
Assert.IsTrue(expressionResult);
Assert.IsNotNull(result);
}