首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >9.1.3 FluentValidation更新是否破坏了验证器的模拟?

9.1.3 FluentValidation更新是否破坏了验证器的模拟?
EN

Stack Overflow用户
提问于 2020-08-22 19:16:19
回答 1查看 654关注 0票数 1

我更新到了9.1.3版本的包,现在我的验证程序模拟无法工作。

不管.Validate()是返回true还是false,代码都会运行。

下面是验证器模拟的代码:

代码语言:javascript
运行
复制
validatorMock
    .Setup(x => x.Validate(It.IsAny<IValidationContext>()).IsValid)
    .Returns(false);

    Assert.Throws<ValidationException>(() => command.Execute(request), "Position field validation error");
    repositoryMock.Verify(repository => repository.EditPosition(It.IsAny<DbPosition>()), Times.Never);

下面是测试失败的地方:

代码语言:javascript
运行
复制
Message: 
      Position field validation error
      Expected: <FluentValidation.ValidationException>
      But was:  null

Validator.cs:

代码语言:javascript
运行
复制
public class SampleValidator : AbstractValidator<Position>
    {
        public SampleValidator()
        {
            RuleFor(position => position.Name)
                .NotEmpty()
                .MaximumLength(80)
                .WithMessage("Position name is too long");

            RuleFor(position => position.Description)
                .NotEmpty()
                .MaximumLength(350)
                .WithMessage("Position description is too long");
        }
    }

依赖注入:

代码语言:javascript
运行
复制
services.AddTransient<IValidator<Position>, SampleValidator>();

用法:

代码语言:javascript
运行
复制
public class SampleCommand : ISampleCommand
    {
        private readonly IValidator<Position> validator;
        private readonly ISampleRepository repository;
        private readonly IMapper<Position, DbPosition> mapper;

        public SampleCommand(
            [FromServices] IValidator<Position> validator,
            [FromServices] ISampleRepository repository,
            [FromServices] IMapper<Position, DbPosition> mapper)
        {
            this.validator = validator;
            this.repository = repository;
            this.mapper = mapper;
        }

        public bool Execute(Position request)
        {
            validator.ValidateAndThrow(request);

            var position = mapper.Map(request);

            return repository.EditPosition(position);
        }
    }

测试中的验证器Mocks:

代码语言:javascript
运行
复制
private Mock<IValidator<EditPositionRequest>> validatorMock;
...
validatorMock = new Mock<IValidator<Position>>();

更新

在更新之前,所有的测试都运行得很完美。现在他们被毁了,我不得不安装上一个版本。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2020-08-24 10:48:15

关于我的评论,请扩展如下:

是的,9.1更改了抛出验证异常的处理方式。

一些背景:

验证器类返回带有ValidationResult布尔属性的IsValidValidateAndThrow扩展方法检查此属性,并在IsValid为false时抛出异常。如果您模拟验证器,您仍然可以使用模拟上的“真正”ValidateAndThrow扩展方法来抛出一个异常,如果模拟返回无效的验证结果。

在FluentValidation 9.1中,抛出异常的逻辑从扩展方法移到RaiseValidationException中的验证器类本身。这样做是为了使抛出异常的逻辑可以自定义(通过重写此方法),这在以前是扩展方法时是无法完成的。

代码语言:javascript
运行
复制
// This is the ValidateAndThrow method definition versions older than 9.1
public static void ValidateAndThrow<T>(this IValidator<T> validator, T instance) {
  var result = validator.Validate(instance);
  
  if (!result.IsValid) {
    throw new ValidationException(result.Errors);
  }
}

// This is the ValidateAndThrowMethod in 9.1 and newer
public static void ValidateAndThrow<T>(this IValidator<T> validator, T instance) {
  validator.Validate(instance, options => {
    options.ThrowOnFailures();
  });
}

对于运行时使用来说,这并没有什么区别--异常仍然会被抛出(除非您已经重写了该方法以防止这种情况发生)。

但是,这样做的副作用是,如果您依赖的是由扩展方法而不是验证器引发的异常,那么这将产生一个不理想的结果。只有在模拟验证器时才会出现这种情况。现在,当您创建一个模拟时,将不会抛出异常,因为模拟并不是一个真正的验证器。

我对FluentValidation的建议一直是“不要模拟验证器”,而是将它们视为黑匣子,并为测试目的提供有效/无效输入的实际验证器实例--从长远来看,这将导致更不脆弱的测试。但是,我也知道,如果您已经有了大量的测试,就不可能以这种方式重写您的测试。

作为解决办法,您可以模拟接受一个ValidateValidationContext的重载,并检查ThrowOnFailures属性的上下文,如果设置为true,则让模拟抛出异常。

但是,请注意,如果您这样做,您可能会遇到一种情况,即您的模拟以一种方式运行,而真正的验证器的行为则有所不同(如果它的RaiseValidationException消息已被覆盖)。

由于这是一个重大的变化,它不应该是在一个主要的版本吗?理想情况下,是的,这是我的错,因为我没有预见到这个特定的用例。

编辑:下面是一个创建模拟以检查ThrowOnFailures属性的示例。该示例使用Moq库,但同样的概念也适用于其他模拟库。

代码语言:javascript
运行
复制
private static Mock<IValidator<T>> CreateFailingMockValidator<T>() {
  var mockValidator = new Mock<IValidator<T>>();

  var failureResult = new ValidationResult(new List<ValidationFailure>() {
    new ValidationFailure("Foo", "Bar")
  });

  // Setup the Validate/ValidateAsync overloads that take an instance.
  // These will never throw exceptions.
  mockValidator.Setup(p => p.Validate(It.IsAny<T>()))
    .Returns(failureResult).Verifiable();
  mockValidator.Setup(p => p.ValidateAsync(It.IsAny<T>(), It.IsAny<CancellationToken>()))
    .ReturnsAsync(failureResult);

  // Setup the Validate/ValidateAsync overloads that take a context.
  // This is the method called by ValidateAndThrow, so will potentially support throwing the exception.
  // Setup method invocations for with an exception and without.
  mockValidator.Setup(p => p.Validate(It.Is<ValidationContext<T>>(context => context.ThrowOnFailures)))
    .Throws(new ValidationException(failureResult.Errors));
  mockValidator.Setup(p => p.ValidateAsync(It.Is<ValidationContext<T>>(context => context.ThrowOnFailures), It.IsAny<CancellationToken>()))
    .Throws(new ValidationException(failureResult.Errors));

  // If ThrowOnFailures is false, return the result.
  mockValidator.Setup(p => p.Validate(It.Is<ValidationContext<T>>(context => !context.ThrowOnFailures)))
    .Returns(failureResult).Verifiable();
  mockValidator.Setup(p => p.ValidateAsync(It.Is<ValidationContext<T>>(context => !context.ThrowOnFailures), It.IsAny<CancellationToken>()))
    .ReturnsAsync(failureResult);

  return mockValidator;
}
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/63540185

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档