How to unit test Command Handler in CQRS pattern in C#
Asked Answered
G

1

7

I'm learning and practicing CQRS and Unit Test in C#. I need to know how to unit test Command Handlers in CQRS.

Here is my CreateAuthorCommand:

public sealed class CreateAuthorCommand : ICommand<Author>
{
    public CreateAuthorCommand(string firstName, string lastName, DateTimeOffset dateOfBirth, string mainCategory, IEnumerable<CreateBookDto> books)
    {
        FirstName = firstName;
        LastName = lastName;
        DateOfBirth = dateOfBirth;
        MainCategory = mainCategory;
        Books = books;
    }

    public string FirstName { get; }
    public string LastName { get; }
    public DateTimeOffset DateOfBirth { get; private set; }
    public string MainCategory { get; }
    public IEnumerable<CreateBookDto> Books { get; }

    [AuditLog]
    [DatabaseRetry]
    internal sealed class AddCommandHandler : ICommandHandler<CreateAuthorCommand, Author>
    {
        private readonly IUnitOfWork _unitOfWork;

        public AddCommandHandler(IUnitOfWork unitOfWork)
        {
            _unitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
        }

        public async Task<Result<Author>> Handle(CreateAuthorCommand command)
        {
            var nameResult = Name.Create(command.FirstName, command.LastName);
            var birthDateResult = BirthDate.Create(command.DateOfBirth);
            var mainCategoryResult = Entities.Authors.MainCategory.Create(command.MainCategory);

            var authorResult = Result.Combine(nameResult, birthDateResult, mainCategoryResult)
                                .Map(() => new Author(nameResult.Value, birthDateResult.Value, null, mainCategoryResult.Value));
                
            if (authorResult.IsFailure)
                return Result.Failure<Author>(authorResult.Error);

            await _unitOfWork.AuthorRepository.AddAsync(authorResult.Value);

            await _unitOfWork.SaveChangesAsync();

            return Result.Success(authorResult.Value);
        }
    }
}

Here is my Unit Test:

public class CreateAuthorCommandTests
{
    [Fact]
    public void Create_Author_Should_Call_Add_Method_Once() 
    {
        var fixture = new Fixture();
        var command = fixture.Create<CreateAuthorCommand>();
        var mockUnitOfWork = new Mock<IUnitOfWork>();
        var mockHandler = new Mock<ICommandHandler<CreateAuthorCommand, Author>>();

        var result = mockHandler.Object.Handle(command);

        mockUnitOfWork.Verify(x => x.AuthorRepository.AddAsync(It.IsAny<Author>()), Times.Once);
    }
}

When I debug the above test,

  1. I'm not able to step into Handler Method!!
  2. How to pass mockUnitOfWork as constructor parameter to AddCommandHandler?

If I can pass mockUnitOfWork, the I can verify the AddAsync call inside my AuthorRepository. Please assist on what I'm doing wrong.

Grogram answered 15/8, 2020 at 18:23 Comment(5)
I understand making the handler sealed, but not why it is internal.Aeromedical
If the handler is public, problem solved.Aeromedical
Also in a unit test, try to avoid mocking the subject under test.Aeromedical
but it was made internal to avoid creating instance of it by mistake. In program flow reflection is used to dynamically create instance of handler and dispatch the commandGrogram
looks like I need to create the instance of AddCommandHandler using reflectionGrogram
T
9

You are testing the handler, so instead of this

var result = mockHandler.Object.Handle(command);

...create an actual instance of AddCommandHandler and inject the dependencies it requires, i.e.

var mockUnitOfWork = new Mock<IUnitOfWork>();
var handler = new AddCommandHandler(mockUnitOfWork.Object);

When you call in to Handle, you'll now be stepping into your implementation.

If you want to keep the Internal access modifier, you can use InternalsVisibleTo attribute to grant access to your test project, e.g. do this in your project that defines the Handler,

[assembly: InternalsVisibleTo(“UnitTests”)]

https://anthonygiretti.com/2018/06/27/how-to-unit-test-internal-classes-in-net-core-applications/

Toile answered 15/8, 2020 at 18:56 Comment(1)
That works. Thanks for teaching. [assembly: InternalsVisibleTo(“UnitTests”)] matters. Respect Earned.Grogram

© 2022 - 2024 — McMap. All rights reserved.