using FluentAssertions; using FluentValidation; using FluentValidation.Results; using MediatR; using Moq; using Shared.Application.Behaviors; namespace Shared.Application.Tests.Behaviors; public class ValidationBehaviorTests { private static ValidationBehavior CreateBehavior( params IValidator[] validators ) => new(validators); [Fact] public async Task Handle_WhenRequestPassesAllValidators_InvokesNextUnchanged() { Mock> validatorMock = new(); validatorMock .Setup(v => v.ValidateAsync( It.IsAny>(), It.IsAny() ) ) .ReturnsAsync(new ValidationResult()); ValidationBehavior behavior = CreateBehavior( validatorMock.Object ); ValidationTestRequest request = new("aaron"); ValidationTestResponse response = new("ok"); bool nextInvoked = false; RequestHandlerDelegate next = () => { nextInvoked = true; return Task.FromResult(response); }; ValidationTestResponse result = await behavior.Handle( request, next, CancellationToken.None ); nextInvoked.Should().BeTrue(); result.Should().BeSameAs(response); } [Fact] public async Task Handle_WhenValidatorReportsFailures_ThrowsValidationExceptionAndNeverInvokesNext() { ValidationFailure failure = new("Name", "is required"); Mock> validatorMock = new(); validatorMock .Setup(v => v.ValidateAsync( It.IsAny>(), It.IsAny() ) ) .ReturnsAsync(new ValidationResult([failure])); ValidationBehavior behavior = CreateBehavior( validatorMock.Object ); ValidationTestRequest request = new(""); bool nextInvoked = false; RequestHandlerDelegate next = () => { nextInvoked = true; return Task.FromResult(new ValidationTestResponse("ok")); }; Func act = async () => await behavior.Handle(request, next, CancellationToken.None); (await act.Should().ThrowAsync()).Which.Errors.Should() .ContainSingle() .Which.Should() .BeSameAs(failure); nextInvoked.Should().BeFalse(); } [Fact] public async Task Handle_WhenMultipleValidatorsAreRegistered_RunsAllOfThem() { Mock> firstValidatorMock = new(); firstValidatorMock .Setup(v => v.ValidateAsync( It.IsAny>(), It.IsAny() ) ) .ReturnsAsync(new ValidationResult()); Mock> secondValidatorMock = new(); secondValidatorMock .Setup(v => v.ValidateAsync( It.IsAny>(), It.IsAny() ) ) .ReturnsAsync(new ValidationResult()); ValidationBehavior behavior = CreateBehavior( firstValidatorMock.Object, secondValidatorMock.Object ); ValidationTestRequest request = new("aaron"); RequestHandlerDelegate next = () => Task.FromResult(new ValidationTestResponse("ok")); await behavior.Handle(request, next, CancellationToken.None); firstValidatorMock.Verify( v => v.ValidateAsync( It.IsAny>(), It.IsAny() ), Times.Once() ); secondValidatorMock.Verify( v => v.ValidateAsync( It.IsAny>(), It.IsAny() ), Times.Once() ); } [Fact] public async Task Handle_WhenMultipleValidatorsReportFailures_AggregatesErrorsFromAll() { ValidationFailure firstFailure = new("Name", "is required"); ValidationFailure secondFailure = new("Name", "must be at least 3 characters"); Mock> firstValidatorMock = new(); firstValidatorMock .Setup(v => v.ValidateAsync( It.IsAny>(), It.IsAny() ) ) .ReturnsAsync(new ValidationResult([firstFailure])); Mock> secondValidatorMock = new(); secondValidatorMock .Setup(v => v.ValidateAsync( It.IsAny>(), It.IsAny() ) ) .ReturnsAsync(new ValidationResult([secondFailure])); ValidationBehavior behavior = CreateBehavior( firstValidatorMock.Object, secondValidatorMock.Object ); ValidationTestRequest request = new(""); RequestHandlerDelegate next = () => Task.FromResult(new ValidationTestResponse("ok")); Func act = async () => await behavior.Handle(request, next, CancellationToken.None); (await act.Should().ThrowAsync()).Which.Errors.Should() .BeEquivalentTo([firstFailure, secondFailure]); } [Fact] public async Task Handle_WhenNoValidatorsAreRegistered_InvokesNext() { ValidationBehavior behavior = CreateBehavior(); ValidationTestRequest request = new("aaron"); ValidationTestResponse response = new("ok"); RequestHandlerDelegate next = () => Task.FromResult(response); ValidationTestResponse result = await behavior.Handle( request, next, CancellationToken.None ); result.Should().BeSameAs(response); } } public record ValidationTestRequest(string Name) : IRequest; public record ValidationTestResponse(string Value);