简体   繁体   English

传递表达式的单元测试查询 <Func<T,bool> &gt;到存储库

[英]Unit Testing Queries that passes Expression<Func<T,bool>> to Repository

I'm having an issue really testing the Querying side of my architecture where I'm calling into a repository that expects an Expression<Func<T, bool>> as parameter for filtering. 我在真正测试我的体系结构的查询方面遇到一个问题,在这里我正在调用一个将Expression<Func<T, bool>>作为过滤参数的存储库。 I was trying to understand this article , where Mark is saying to use Stubs for queries instead. 我试图理解本文 ,其中Mark表示要使用存根代替查询。

Lets say I have a query handler: 可以说我有一个查询处理程序:

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

Now my test is going to look something like this: 现在我的测试看起来像这样:

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

To me, this test is useless, especially using It.IsAny<Expression<Func<User, bool>>>() as I could be filtering by anything at all. 对我而言,此测试毫无用处,尤其是使用It.IsAny<Expression<Func<User, bool>>>()因为我可能会过滤所有内容。 The filter would be a crucial business logic that needs to be tested. 筛选器将是需要测试的关键业务逻辑。 How can I test an expression like this? 如何测试这样的表达式? Is this one of those reasons why a generic repository is bad and I should use a specific repository that take exactly the filter parameters that is needed? 这是通用存储库不好的原因之一,我应该使用一个特定的存储库,该存储库完全采用所需的过滤器参数吗? If that is the case, I'll be moving the expression from one layer to the other and I'd still need to test it 如果是这种情况,我会将表达式从一层移到另一层,但仍然需要对其进行测试

If I should use stubs as Mark said in his blog, are there any examples out there? 如果我应该像Mark在博客中所说的那样使用存根,那有没有例子? Am I supposed to run this query on an in-memory list which will be used to validate that the filter expression is correct? 我是否应该在内存列表中运行此查询,该列表将用于验证过滤器表达式是否正确?

Is this one of those reasons why a generic repository is bad 这是通用存储库不好的那些原因之一

Yes, it is. 是的。 Additionally, if you want to follow the SOLID principles of OOD (not that you must , but they are often useful in order to understand the consequences of design decisions), "clients […] own the abstract interfaces" ( Agile Principles, Patterns, and Practices , chapter 11). 此外,如果您要遵循OODSOLID原则 (并非必须这样做 ,但它们通常对于理解设计决策的结果很有用), “客户[...]拥有抽象接口”敏捷原则,模式,与实践 ,第11章)。 Thus, the interface used by the client should be defined in terms of what the client needs , not by what some general-purpose library exposes. 因此,应该根据客户端的需求而不是某些通用库公开的内容来定义客户端使用的接口。

In this particular case, it looks like what the GetUserByEmailQueryHandler really needs is an ability to query based on an email address, so you could define a reader interface like this: 在这种特殊情况下, GetUserByEmailQueryHandler真正需要的是一种能够基于电子邮件地址进行查询的功能,因此您可以定义一个阅读器接口,如下所示:

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

This turns GetUserByEmailQueryHandler into something like this: 这会将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);
    }
}

At this point, the GetUserByEmailQueryHandler class is so degenerate that you should seriously consider whether it adds any value. 此时, GetUserByEmailQueryHandler类非常GetUserByEmailQueryHandler ,您应该认真考虑它是否增加了任何值。

The filter would be a crucial business logic that needs to be tested. 筛选器将是需要测试的关键业务逻辑。 How can I test an expression like this? 如何测试这样的表达式?

That really depends on where you want that business logic to execute in the final system. 这确实取决于想要的业务逻辑在最终的系统执行。 You could test a filter by running it in memory, but if you ultimately plan on executing it on a database server, you'll have to involve the database in your automated tests . 您可以通过在内存中运行过滤器来对其进行测试,但是如果最终计划在数据库服务器上执行过滤器,则必须将数据库包括在自动化测试中 That tends to suck, which is the case why most programmers are seriously looking for alternatives to relational databases. 这往往很糟糕,这就是为什么大多数程序员都在认真寻找关系数据库的替代品的原因。

Sorry, but if there's a solution to this particular problem, I don't know what it is. 抱歉,但是如果有解决此特定问题的方法,我不知道这是什么。

Personally, I design my system so that they don't rely on complicated filter expressions, but only on simple filter expressions that I can treat as Humble Objects . 我个人设计了系统,以使它们不依赖复杂的过滤器表达式,而仅依赖于我可以视为Humble Objects的 简单过滤器表达式。

To me, this test is useless, especially using It.IsAny<Expression<Func<User, bool>>>() as I could be filtering by anything at all. 对我而言,此测试毫无用处,尤其是使用It.IsAny<Expression<Func<User, bool>>>()因为我可能会过滤所有内容。 The filter would be a crucial business logic that needs to be tested. 筛选器将是需要测试的关键业务逻辑。 How can I test an expression like this? 如何测试这样的表达式?

No matter what abstraction is used it is needed to test filtering business logic . 无论使用哪种抽象,都需要测试过滤业务逻辑 I answered the similar SO question "Why this mock with Expression is not matching?" 我回答了类似的SO问题“为什么带有Expression的该模拟不匹配?” a year ago and you can use a code example from it. 一年前,您可以使用其中的代码示例。

In order to test filtering business logic for your design I would change your code the following way: 为了测试过滤设计的业务逻辑,我将通过以下方式更改代码:

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

You can use Return method which allows you to access passed expression and apply it to any target collection of users. 您可以使用Return方法,该方法允许您访问传递的表达式并将其应用于用户的任何目标集合。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM