3

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. I was trying to understand this article, where Mark is saying to use Stubs for queries instead.

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. 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? Am I supposed to run this query on an in-memory list which will be used to validate that the filter expression is correct?

Shawn Mclean
  • 56,733
  • 95
  • 279
  • 406
  • The query handler implementation should use the persistence directly, NOT a repository. A query handler is in 'spirit' just a repository method. – MikeSW Oct 29 '14 at 21:07

2 Answers2

4

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). 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:

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

This turns GetUserByEmailQueryHandler into something like this:

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.

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.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
4

To me, this test is useless, especially using It.IsAny<Expression<Func<User, bool>>>() as I could be filtering by anything at all. 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?" 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.

Community
  • 1
  • 1
Ilya Palkin
  • 14,687
  • 2
  • 23
  • 36