【问题标题】:Unit Testing Queries that passes Expression<Func<T,bool>> to Repository将 Expression<Func<T,bool>> 传递给存储库的单元测试查询
【发布时间】:2014-10-11 06:14:36
【问题描述】:

我在真正测试架构的查询端时遇到了问题,我正在调用一个需要Expression&lt;Func&lt;T, bool&gt;&gt; 作为过滤参数的存储库。我试图理解这个article,马克说要使用存根进行查询。

假设我有一个查询处理程序:

public class GetUserByEmailQueryHandler : IQueryHandler<GetUserByEmailQuery, User>
{
    private readonly IGenericRepository<User> userRepository;

    public GetUserByEmailQueryHandler(IGenericRepository<User> userRepository)
    {
        this.userRepository = userRepository;
    }
    public User Handle(GetUserByEmailQuery query)
    {
        return this.userRepository.Find(u => u.Email == query.Email && u.IsLockedOut == false);
    }
}

现在我的测试将如下所示:

    [Fact]
    public void Correctly_Returns_Result()
    {
        // arrange
        var id = Guid.NewGuid();
        var email = "test@test.com";
        var userRepositoryMock = new Mock<IGenericRepository<User>>();
        userRepositoryMock.Setup(
            r =>
                r.Find(It.IsAny<Expression<Func<User, bool>>>())).Returns(new User { UserId = id }).Verifiable();

        // Act
        var query = new GetUserByEmailQuery(email);
        var queryHandler = new GetUserByEmailQueryHandler(userRepositoryMock.Object);
        var item = queryHandler.Handle(query);

        // Assert
        userRepositoryMock.Verify();
        Assert.Equal(id, item.UserId);
    }

对我来说,这个测试毫无用处,尤其是使用It.IsAny&lt;Expression&lt;Func&lt;User, bool&gt;&gt;&gt;(),因为我可以过滤任何东西。过滤器将是需要测试的关键业务逻辑。如何测试这样的表达式?这是通用存储库不好的原因之一吗?我应该使用一个特定的存储库,该存储库完全采用所需的过滤器参数?如果是这种情况,我会将表达式从一层移动到另一层,我仍然需要对其进行测试

如果我应该像 Mark 在他的博客中所说的那样使用存根,是否有任何示例?我是否应该在内存列表上运行此查询,以验证过滤器表达式是否正确?

【问题讨论】:

  • 查询处理程序实现应该直接使用持久性,而不是存储库。查询处理程序在“精神”中只是一个存储库方法。

标签: c# domain-driven-design repository-pattern


【解决方案1】:

这是通用存储库不好的原因之一吗

是的,是的。此外,如果您想遵循 SOLID principles of OOD(不是您必须,但它们通常有助于了解设计决策的后果),“客户 [...]抽象接口”Agile Principles, Patterns, and Practices,第 11 章)。因此,客户端使用的接口应该根据客户端需要来定义,而不是根据某些通用库公开的内容来定义。

在这种特殊情况下,看起来GetUserByEmailQueryHandler 真正需要的是基于电子邮件地址进行查询的能力,因此您可以定义这样的阅读器界面:

public interface IUserReader
{
    User FindByEmail(string email);
}

这会将GetUserByEmailQueryHandler 变成这样的:

public class GetUserByEmailQueryHandler : IQueryHandler<GetUserByEmailQuery, User>
{
    private readonly IUserReader userRepository;

    public GetUserByEmailQueryHandler(IUserReader userRepository)
    {
        this.userRepository = userRepository;
    }
    public User Handle(GetUserByEmailQuery query)
    {
        return this.userRepository.FindByEmail(query.Email);
    }
}

此时,GetUserByEmailQueryHandler 类已经非常退化,你应该认真考虑它是否增加了任何价值。

过滤器将是需要测试的关键业务逻辑。如何测试这样的表达式?

这实际上取决于您希望该业务逻辑在最终系统中执行的位置。您可以通过在内存中运行过滤器来测试它,但如果您最终计划在数据库服务器上执行它,您必须将数据库纳入您的自动化测试。这往往很糟糕,这就是为什么大多数程序员都在认真寻找关系数据库的替代品的原因。

抱歉,如果有解决这个特定问题的方法,我不知道它是什么。

就我个人而言,我设计我的系统以便它们不依赖于复杂过滤器表达式,而仅依赖于我可以视为Humble Objects简单过滤器表达式。

【讨论】:

    【解决方案2】:

    对我来说,这个测试没有用,尤其是使用 It.IsAny&lt;Expression&lt;Func&lt;User, bool&gt;&gt;&gt;() 因为我可以过滤 什么都没有。过滤器将是一个关键的业务逻辑, 需要测试。如何测试这样的表达式?

    无论使用什么抽象都需要测试过滤业务逻辑。我在一年前回答了the similar SO question "Why this mock with Expression is not matching?",您可以使用其中的代码示例。

    为了测试您设计的过滤业务逻辑,我将通过以下方式更改您的代码:

    [Fact]
    public void Correctly_Returns_Result()
    {
        // Arrange
        var validEmail = "test@test.com";
    
        var userThatMatches = new User { UserId = Guid.NewGuid(), Email = validEmail, IsLockedOut = false };
        var userThatDoesnotMatchByIsLockedOut = new User { UserId = Guid.NewGuid(), Email = validEmail, IsLockedOut = false };
        var userThatDoesnotMatchByEmail = new User { UserId = Guid.NewGuid(), Email = "Wrong Email", IsLockedOut = true };
    
        var aCollectionOfUsers = new List<User>
        {
            userThatMatches,
            userThatDoesnotMatchByIsLockedOut,
            userThatDoesnotMatchByEmail
        };
    
        var userRepositoryMock = new Mock<IGenericRepository<User>>();
        userRepositoryMock
            .Setup(it => it.Find(It.IsAny<Expression<Func<User, bool>>>()))
            .Returns<Expression<Func<User, bool>>>(predicate =>
            {
                return aCollectionOfUsers.Find(user => predicate.Compile()(user));
            });
    
            var sut = new GetUserByEmailQueryHandler(
                userRepositoryMock.Object);
    
        // Act
        var foundUser = sut.Handle(new GetUserByEmailQuery(validEmail));
    
        // Assert
        userRepositoryMock.Verify();
        Assert.Equal(userThatMatches.UserId, foundUser.UserId);
    }
    

    您可以使用Return 方法,该方法允许您访问传递的表达式并将其应用于任何目标用户集合。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多