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 ); TestRequest request = new("aaron", "pa$$word123"); TestResponse response = new("ok"); bool nextInvoked = false; RequestHandlerDelegate next = () => { nextInvoked = true; return Task.FromResult(response); }; TestResponse 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("Username", "is required"); Mock> validatorMock = new(); validatorMock .Setup(v => v.ValidateAsync( It.IsAny>(), It.IsAny() ) ) .ReturnsAsync(new ValidationResult([failure])); ValidationBehavior behavior = CreateBehavior( validatorMock.Object ); TestRequest request = new("", "pa$$word123"); bool nextInvoked = false; RequestHandlerDelegate next = () => { nextInvoked = true; return Task.FromResult(new TestResponse("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 ); TestRequest request = new("aaron", "pa$$word123"); RequestHandlerDelegate next = () => Task.FromResult(new TestResponse("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("Username", "is required"); ValidationFailure secondFailure = new("Username", "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 ); TestRequest request = new("", "pa$$word123"); RequestHandlerDelegate next = () => Task.FromResult(new TestResponse("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(); TestRequest request = new("aaron", "pa$$word123"); TestResponse response = new("ok"); RequestHandlerDelegate next = () => Task.FromResult(response); TestResponse result = await behavior.Handle( request, next, CancellationToken.None ); result.Should().BeSameAs(response); } }