Search code examples
c#.netentity-frameworkunit-testingmoq

How to unit test a handler that does not use the repositories?


Assume I have a handler that is implemented this way. There are no repositories and the logic is written inside the handler in the following manner.

Since I should not be mocking EF, how would one unit test this, since it does contain logic.

Example - I have shortened the code:

public record GetUsersQuery : IRequest<UserResult>
{
    public List<string> UserType { get; set; }
}

internal sealed class GetUsersQueryHandler : IRequestHandler<GetUsersQuery, UserResult>
{
    private readonly IDbContext context;

    public GetUsersQueryHandler(IDbContext context)
    {
        this.context = context;
    }

    public async Task<UserResult> Handle(GetUsersQuery query, CancellationToken cancellationToken)
    {
        var baseQuery = context.AppUsers.AsQueryable();

        baseQuery = baseQuery.Where(user => query.UserType.Contains(user.UserType));

        /*
            More Logic
        */

        result = await baseQuery.ToListAsync(cancellationToken);

        return MapToUserResult(result);
    }
}

Solution

  • This is difficult, but not impossible, to test. I can think of two options.

    Write integration tests instead

    If you can't change the code as it is, you may consider writing the tests so that the code also uses the database. Some people will call this integration tests rather than unit tests, but if done right, you'll still have automatic test coverage of your code.

    The trick is to do it right, because databases are persistent. Thus, you can't rely on garbage collection to clean up after each test. Instead, you must explicitly make sure that each test doesn't leave behind state that will impact the next test.

    Additionally, you may also need to automate the configuration of the database before each test runs. None of this is impossible, but on the other hand is not trivial, and tends to make the tests slow. While I do use this technique, I tend to take the test pyramid into account: Don't overdo it.

    There are many different ways to do this, even on .NET. I describe one such way in my book Code That Fits in Your Head.

    Apply the Dependency Inversion Principle

    You may regard Entity Framework as an implementation detail. I do.

    According to the Dependency Inversion Principle, client code should depend on abstractions instead of implementation details. Client code should define what the abstraction should look like, instead of the implementation details leaking through the abstractions.

    In this case, it looks as though the GetUsersQueryHandler class really needs a way to query a repository to get some users with a particular type(?)

    So, instead, define an interface that does that (and only that), and inject that into GetUsersQueryHandler instead of IDbContext. Move the IDbContext, the Where clause etc. to the implementation of the new interface.