.net core FluentValidation:模拟ValidationResult.IsValid

.net core FluentValidation:模拟ValidationResult.IsValid,.net-core,asp.net-core-webapi,fluentvalidation,.net Core,Asp.net Core Webapi,Fluentvalidation,我正在dotnetcore2中对WebAPI使用FluentValidation。我已经成功地为验证器编写了测试,但现在我正试图为我的控制器模拟验证器。控制器如下: [Route("[controller]")] public class SecurityController : Controller { private readonly IValidator<AuthenticateRequest> _authenticateRequestValidator; p

我正在dotnetcore2中对WebAPI使用FluentValidation。我已经成功地为验证器编写了测试,但现在我正试图为我的控制器模拟验证器。控制器如下:

[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
    }
}
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");
    }
}
验证程序如下所示:

[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
    }
}
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");
    }
}
公共类AuthenticateRequestValidator:AbstractValidator{
/// 
///为提供验证程序
/// 
公共AuthenticateRequestValidator(){
RuleFor(x=>x.Username)
.NotNull()
.NotEmpty()
.WithMessage(“需要用户名”);
规则(x=>x.Password)
.NotNull()
.NotEmpty()
.WithMessage(“需要密码”);
}
}
它被注入到带有点网核心标准DI的控制器中。不发布代码,因为它与此问题无关,因为它是一个测试问题

我正在用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);
    }
}
公共类安全控制器测试{
私有只读iTexture Fixture=new Fixture().Customize(新的AutoMoqCustomization{ConfigureMembers=true});
私有只读模拟authenticateRequestValidatorMock;
公共安全管理员(){
authenticateRequestValidatorMock=Mock.Get(Fixture.Create());
}
[事实]
公共异步任务身份验证\u validateRequest(){
//安排
var request=Fixture.Create();
authenticateRequestValidatorMock
.Setup(x=>x.ValidateAsync(It.Is(v=>v==request),默认值(CancellationToken)))
.Returns(()=>Fixture.Create())
.可验证();
var控制器=新的SecurityController(authenticationServiceMock.Object、tokenisationServiceMock.Object、authenticateRequestValidatorMock.Object);
//表演
等待控制器。验证同步(请求);
//断言
authenticateRequestValidatorMock.Verify();
}
[事实]
公共异步任务身份验证\u返回400\u WhenUserName验证失败(){
//安排
var request=Fixture.Create();
var validationResultMock=new Mock();
验证结果锁
.SetupGet(x=>x.IsValid)
.返回(()=>true);
authenticateRequestValidatorMock
.Setup(x=>x.ValidateAsync(It.Is(v=>v==request),默认值(CancellationToken)))
.返回(()=>新任务(()=>validationResultMock.Object));
var控制器=新的SecurityController(authenticationServiceMock.Object、tokenisationServiceMock.Object、authenticateRequestValidatorMock.Object);
//表演
var result=await controller.authenticateSync(请求);
//断言
var badRequestResult=Assert.IsType(结果);
IsType(badRequestResult.Value);
}
}
我需要模拟
ValidationResult
,这样我就可以忽略实际的验证器逻辑(在别处测试)并测试控制器逻辑。注入了许多其他依赖项,以及更多的代码,但粘贴的代码是问题的症结所在,当剥离所有其他内容时,会产生相同的结果

第一个测试通过,第二个测试在点击
var validator=wait\u authenticateRequestValidator.validateSync(req)时永远运行

值得注意的是,ValidationResult.IsValid是一个虚拟只读属性


第二个测试有什么问题?

您是否尝试了Asp.Net核心-FluentValidation集成?通过这种方式,您不需要将验证程序依赖项传递给构造函数

FluentValidation在验证错误的情况下填充ModelState,您可以像这样使用它

if (!ModelState.IsValid)
{
    return BadRequest(ModelState);
}
为了测试它,您需要设置控制器的ModelState

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");
var controller=new SecurityController(authenticationServiceMock.Object、tokenisationServiceMock.Object、authenticateRequestValidatorMock.Object);
controller.ModelState.AddModelError(“测试”、“测试”);
//表演
IActionResult actionResult=等待控制器.authenticateSync(请求);
var badRequestObjectResult=actionResult作为badRequestObjectResult;
Assert.NotNull(badRequestObjectResult);
var serializableError=badRequestObjectResult.Value为serializableError;
//断言
Assert.NotNull(结果);
var badRequestResult=Assert.IsType(结果);
var serializableError=assert.IsType(badRequestResult.Value)
Assert.True(((字符串[])serializableError[“test”])[0]=“test”);
我认为,将ModelState留空足以忽略实际的验证器逻辑

FluentValidation还有内置的测试api。您可以单独测试验证逻辑


我在任何地方都没有看到这个,搜索中也没有出现。我会查一查,然后告诉你。正如我所说,我已经编写了验证器逻辑(使用您链接的helper方法)-这是在模拟验证器来测试控制器。对不起,我没有恶意,我没有听到您说过您已经编写了验证测试逻辑。我可能会有点激动,因为这是我对Stackoverflow的第一个回答:)无论如何,我希望我的回答会有帮助。它确实有帮助——我相信这也是正确的方法。非常感谢,标记为已接受!:)