单元测试模拟一类继承多个接口和一个类



首先是代码,

通用接口:

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

现在,这些TestDbAsyncEnumeratorTestDbAsyncQueryProvider从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);
    }

最新更新