user9124444
user9124444

Reputation:

Moq: running flows with FluentValidation

I'm using FluentValidation to validate request. If the validation fails UseCaseHandler should not be invoked. Within the UseCaseHandler I'm usign IRepository, and this is checked to see if the UseCaseHandler gets invoked.

The Request validator

 public class MyValidator: AbstractValidator<Request>
 {
     public MyValidator()
     {
         RuleFor(rq=> rq)
              .Cascade(CascadeMode.Continue);
         RuleFor(rq=> rq)
            .Must(property => property.id != default(Guid))
            .WithMessage(message => $"invalid id.")
            .WithName(member => nameof(member.Id));

      }
 }

This is the test

[Fact]
public async Task Test()
{
     Mock<IUnitOfWork> uowMock = new Mock<IUnitOfWork>();
     Mock<IRepository> repositoryMock = new Mock<IRepository>(MockBehavior.Strict);
     Mock<IValidator<Request>> validatorMock = new Mock<IValidator<Request>>(MockBehavior.Strict);

     var request = new Request
     {
        Id = Guid.NewGuid()
     };

     validatorMock
         .Setup(validator => validator.Validate(request))
         .Returns(new ValidationResult());

     repositoryMock
          .Setup(repo => repo.SaveAsync(It.IsAny<object>()))
          .Returns(Task.CompletedTask);

     var sut = new UseCase(uowMock.Object, repositoryMock.Object, validatorMock.Object);


     Func<Task> act = () => sut.UseCaseHandler(request);


     await act.Should().NotThrowAsync();
     repositoryMock.Verify(repo => repo.SaveAsync(It.IsAny<object>()), Times.Once);
 }

I'm looking to write a test that will check the flow.

If the validation fails the test should fail and SaveAsync should not be called.

If the validation succeeds that the test should succeed also and SaveAsync should be called one time.

What is the way to write a test ?

UPDATE

This is the use case class definition.

UseCaseHandlerProxy is a base abstract class which acts as a proxy

public class UseCase : UseCaseHandlerProxy<Request, Response>
{
    private readonly IRepository _repository;


    public UseCase(IUnitOfWork unitOfWork, IRepository repository, IValidator<Request> validator)
        : base(unitOfWork, validator)
    {
        _repository = repository
    }

    public override async Task<Response> UseCaseHandler(Request request)
    {
        Order order = new Order();

        order.Create();

        await _repository.SaveAsync(order);

        return new Response(order.Id);
    }
}

This is the Request class definition

class Request
{
    public Guid Id { get; set; }
}

Te response only returns the same Id

Proxy class

public abstract class UseCaseHandlerProxy<TRequest, TResponse> : IUseCaseHandler<TRequest, TResponse>
    where TRequest : IRequest
    where TResponse : Response
{
    private IValidator<TRequest> Validator { get; }
    protected internal IUnitOfWork UnitOfWork { get; }


    public UseCaseHandlerProxy(IUnitOfWork unitOfWork, IValidator<TRequest> validator)
    {
        Validator = validator;
        UnitOfWork = unitOfWork;
    }

    async Task<TResponse> IUseCaseHandler<TRequest, TResponse>.HandleAsync(TRequest request)
    {
        ValidationResult validationResult = await Validator.ValidateAsync(request);

        TResponse response;

        if (!validationResult.IsValid)
        {
            response = (TResponse)System.Activator.CreateInstance(typeof(TResponse));

            validationResult.Errors.ToList().ForEach(error => response.AddError(error.PropertyName, error.ErrorMessage));

            return response;
        }

        response = await UseCaseHandler(request);

        return response;
    }

    public abstract Task<TResponse> UseCaseHandler(TRequest request);
}

Upvotes: 3

Views: 5516

Answers (1)

Nkosi
Nkosi

Reputation: 247551

Given the flow you want to test I would say that you are invoking the wrong member.

Cast the sut to IUseCaseHandler<TRequest, TResponse> to get access to HandleAsync which is what does the desired flow.

For example the following verifies that if no validation error that repository invokes save.

[Fact]
public async Task UseCase_Should_Save() {
    //Arrange
    Mock<IUnitOfWork> uowMock = new Mock<IUnitOfWork>();
    Mock<IRepository> repositoryMock = new Mock<IRepository>(MockBehavior.Strict);
    Mock<IValidator<Request>> validatorMock = new Mock<IValidator<Request>>(MockBehavior.Strict);

    var request = new Request {
        Id = Guid.NewGuid()
    };

    validatorMock
        .Setup(validator => validator.ValidateAsync(request, It.IsAny<CancellationToken>()))
        .ReturnsAsync(new ValidationResult());

    repositoryMock
         .Setup(repo => repo.SaveAsync(It.IsAny<object>()))
         .Returns(Task.FromResult((object)null));

    var sut = new UseCase(uowMock.Object, repositoryMock.Object, validatorMock.Object) as IUseCaseHandler<Request, Response>;

    //Act
    Func<Task> act = () => sut.HandleAsync(request);

    //Assert
    await act.Should().NotThrowAsync();
    repositoryMock.Verify(repo => repo.SaveAsync(It.IsAny<object>()), Times.Once);
}

The following verifies that if there are errors, then the repository does not save

[Fact]
public async Task UseCase_Should_Not_Save() {
    //Arrange
    var uowMock = new Mock<IUnitOfWork>();
    var repositoryMock = Mock.Of<IRepository>();
    var validatorMock = new Mock<IValidator<Request>>(MockBehavior.Strict);

    var request = new Request {
        Id = Guid.NewGuid()
    };

    var result = new ValidationResult();
    result.Errors.Add(new ValidationFailure("SomeProperty", "SomeError"));

    validatorMock
        .Setup(validator => validator.ValidateAsync(request, It.IsAny<CancellationToken>()))
        .ReturnsAsync(result);

    var sut = new UseCase(uowMock.Object, repositoryMock, validatorMock.Object) as IUseCaseHandler<Request, Response>;

    //Act
    Func<Task> act = () => sut.HandleAsync(request);

    //Assert
    await act.Should().NotThrowAsync();
    Mock.Get(repositoryMock).Verify(repo => repo.SaveAsync(It.IsAny<object>()), Times.Never);
}

Upvotes: 4

Related Questions