FluentValidation: Mocking ValidationResult.IsValid - PullRequest
0 голосов
/ 10 мая 2018

Я использую FluentValidation с WebAPI в ядре DotNet 2. Я успешно написал тесты для валидатора, но сейчас пытаюсь смоделировать валидатор для моего контроллера.Контроллер выглядит следующим образом:

[Route("[controller]")]
public class SecurityController : Controller {
    private readonly IValidator<AuthenticateRequest> _authenticateRequestValidator;

    public SecurityController(IValidator<AuthenticateRequest> authenticateRequestValidator) {
        _authenticateRequestValidator = authenticateRequestValidator;
    }

    [HttpPost]
    [AllowAnonymous]
    [Route("auth")]
    public async Task<IActionResult> AuthenticateAsync([FromBody] AuthenticateRequest req) {
        // Validate
        var validator = await _authenticateRequestValidator.ValidateAsync(req);
        if(!validator.IsValid) {
            return BadRequest();
        }

        // ...snip
    }
}

AuthenticateRequest выглядит следующим образом:

public class AuthenticateRequest {
    public string Username { get; set; }

    public string Password { get; set; }
}

И валидатор выглядит следующим образом:

public class AuthenticateRequestValidator : AbstractValidator<AuthenticateRequest> {
    /// <summary>
    ///     Provides a validator for <see cref="AuthenticateRequest" />
    /// </summary>
    public AuthenticateRequestValidator() {
        RuleFor(x => x.Username)
            .NotNull()
            .NotEmpty()
            .WithMessage("Username is required");
        RuleFor(x => x.Password)
            .NotNull()
            .NotEmpty()
            .WithMessage("Password is required");
    }
}

Он вводится в контроллер сстандартное DI Dot Net Core.Не публиковать код, поскольку он не имеет отношения к этой проблеме, так как это проблема тестирования.

Я тестирую с xunit, Moq и AutoFixture.Вот два теста:

public class SecurityControllerTests {
    private readonly IFixture Fixture = new Fixture().Customize(new AutoMoqCustomization {ConfigureMembers = true});

    private readonly Mock<IValidator<AuthenticateRequest>> authenticateRequestValidatorMock;

    public SecurityControllerTests() {
        authenticateRequestValidatorMock = Mock.Get(Fixture.Create<IValidator<AuthenticateRequest>>());
    }

    [Fact]
    public async Task Authenticate_ValidatesRequest() {
        // Arrange
        var request = Fixture.Create<AuthenticateRequest>();
        authenticateRequestValidatorMock
            .Setup(x => x.ValidateAsync(It.Is<AuthenticateRequest>(v => v == request), default(CancellationToken)))
            .Returns(() => Fixture.Create<Task<ValidationResult>>())
            .Verifiable();
        var controller = new SecurityController(authenticationServiceMock.Object, tokenisationServiceMock.Object, authenticateRequestValidatorMock.Object);

        // Act
        await controller.AuthenticateAsync(request);

        // Assert
        authenticateRequestValidatorMock.Verify();
    }

    [Fact]
    public async Task Authenticate_Returns400_WhenUsernameValidationFails() {
        // Arrange
        var request = Fixture.Create<AuthenticateRequest>();

        var validationResultMock = new Mock<ValidationResult>();
        validationResultMock
            .SetupGet(x => x.IsValid)
            .Returns(() => true);
        authenticateRequestValidatorMock
            .Setup(x => x.ValidateAsync(It.Is<AuthenticateRequest>(v => v == request), default(CancellationToken)))
            .Returns(() => new Task<ValidationResult>(() => validationResultMock.Object));
        var controller = new SecurityController(authenticationServiceMock.Object, tokenisationServiceMock.Object, authenticateRequestValidatorMock.Object);

        // Act
        var result = await controller.AuthenticateAsync(request);

        // Assert
        var badRequestResult = Assert.IsType<BadRequestObjectResult>(result);
        Assert.IsType<SerializableError>(badRequestResult.Value);
    }
}

Мне нужно смоделировать ValidationResult, чтобы я мог проигнорировать фактическую логику валидатора (которая тестируется в другом месте) и проверить логику контроллера.Внедрено много других зависимостей и гораздо больше кода, но вставленный код является сутью проблемы и дает те же результаты, когда все остальное удаляется.

Первый тест проходит, второй выполняется навсегда, когда он попадаетстрока var validator = await _authenticateRequestValidator.ValidateAsync(req); в контроллере.

Стоит отметить, что ValidationResult.IsValid является виртуальным свойством только для чтения.

Что не так со вторым тестом?

1 Ответ

0 голосов
/ 10 мая 2018

Вы пробовали Asp.Net Core - интеграцию с FluentValidation? Таким образом, вам не нужно передавать зависимости Validator в конструктор.

https://github.com/JeremySkinner/FluentValidation/wiki/i.-ASP.NET-Core-integration

FluentValidation заполняет ModelState в случае ошибки проверки, и вы используете его как;

if (!ModelState.IsValid)
{
    return BadRequest(ModelState);
}

Для тестирования вы устанавливаете ModelState ваших контроллеров Mock

var controller = new SecurityController(authenticationServiceMock.Object, tokenisationServiceMock.Object, authenticateRequestValidatorMock.Object);
controller.ModelState.AddModelError("test", "test");

// Act
IActionResult actionResult =  await controller.AuthenticateAsync(request);

var badRequestObjectResult = actionResult as BadRequestObjectResult;

Assert.NotNull(badRequestObjectResult);

var serializableError = badRequestObjectResult.Value as SerializableError;

// Assert
Assert.NotNull(result);
var badRequestResult = Assert.IsType<BadRequestObjectResult>(result);
var serializableError = assert.IsType<SerializableError>(badRequestResult.Value)
Assert.True(((string[])serializableError["test"])[0] == "test");

Оставить ModelState пустым было бы достаточно, чтобы игнорировать реальную логику валидатора, я думаю.

Также у FluentValidation есть встроенный интерфейс тестирования API. Вы можете проверить свою логику проверки отдельно.

https://github.com/JeremySkinner/FluentValidation/wiki/g.-Testing

...