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

时间:2017-10-03 15:24:38

标签: c# entity-framework unit-testing async-await moq

首先是代码,

通用接口:

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.基本上,我没有设置FirstOrDefaultAsync mockSet GetLoginDetailAsyncEntityService调用FirstOrDefaultAsync }},最终调用IDbSet的{​​{1}}。

我不知道如何模仿,因为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的答案之后,我正在更新它。该测试在答案中指定正常,但现在另一个测试失败了。如果特定用户退出系统,我想测试。

这是测试代码:       [测试]         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);
    }

此测试应该通过,因为它应该在userAccount上查询但是在我调试时它失败了,它进入了LoginService,而我检查了_entityService.Get().ToList()它说0计数,而它应该真的说计数1,我设置的userAccount 。 Afaik,IDbSet仍未设置,这就是为什么计数为0,并且它没有返回true。我该如何设置?如果它是正确的,那么为什么这个测试失败了?另外,我知道moq对于测试表达式并不是很好,但我从here获得了predicate部分代码。

1 个答案:

答案 0 :(得分:2)

我同意@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);
    }