簡體   English   中英

單元測試 Mock 一個繼承多個接口和一個類的類

[英]Unit Test Mock a class inheriting multiple interfaces and a class

先上代碼,

通用接口:

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. 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. 基本上,我沒有安裝的FirstOrDefaultAsyncmockSet這是獲得所謂的GetLoginDetailAsync (它調用EntityService ,那最終調用的FirstOrDefaultAsyncIDbSet )。

我不知道如何模擬它,因為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 的回答之后,我正在更新這個。 該測試按照答案中的說明運行良好,但現在另一個測試失敗了。 我想測試一下,系統中是否存在特定用戶。

這是測試代碼: [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);
    }

這個測試應該通過,因為它應該查詢userAccount但它失敗了,當我調試時,它進入LoginService我檢查了_entityService.Get().ToList()它說 0 count,而它真的應該說計數 1,我設置的userAccount Afaik, IDbSet仍未設置,這就是計數為 0 且未返回 true 的原因。 我該如何設置? 如果它是正確的,那么為什么這個測試失敗了? 另外,我知道moq對於測試表達式並不是很好,但我從這里得到了代碼的這個predicate部分。

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM