【问题标题】:Spring unit test issue with ValidatorValidator 的 Spring 单元测试问题
【发布时间】:2018-10-10 19:31:22
【问题描述】:

我正在尝试为我拥有的验证器类编写单元测试。所以在我的 UniqueEmailValidator 类中,我注入了一个@Service 组件来检查它是否存在。

@AllArgsConstructor
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {
    
    private final AccountService accountService;
    
    @Override
    public void initialize(final UniqueEmail constraintAnnotation) {
    }

    @Override
    public boolean isValid(final String email, final ConstraintValidatorContext context) {
        return !this.accountService.findByEmail(email).isPresent();
    }
}

@Documented
@Target({TYPE, FIELD, ANNOTATION_TYPE}) 
@Retention(RUNTIME)
@Constraint(validatedBy = UniqueEmailValidator.class)
public @interface UniqueEmail {
    
    String message() default "{com.x.x.validator.UniqueEmail.message}";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

我尝试为此约束验证器编写单元测试。

@RunWith(SpringRunner.class)
@DataJpaTest
public class AccountValidatorTest {
    
    private static Validator validator;

    @BeforeClass
    public static void setUp() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        validator = factory.getValidator();
    }
    
    @Autowired
    private AccountService accountService;
        
    @Test
    public void shouldDetectDuplicatedEmailAddress() {
        
        User user = new User(); 
        // Setters omit
        
        // accountRepository.save(user);
                        
        Set<ConstraintViolation<AccountRegistrationForm>> violations = validator.validate(user);
        
        assertEquals(1, violations.size());
    }

}

如何在 Validator 类中初始化 AccountService?似乎它没有被注入,因此是空异常。这是踪迹。

javax.validation.ValidationException: HV000064: Unable to instantiate ConstraintValidator: com.x.x.validator.UniqueEmailValidator.
    at org.hibernate.validator.internal.util.privilegedactions.NewInstance.run(NewInstance.java:43)
    at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorFactoryImpl.run(ConstraintValidatorFactoryImpl.java:43)
    at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorFactoryImpl.getInstance(ConstraintValidatorFactoryImpl.java:28)
    at org.hibernate.validator.internal.engine.constraintvalidation.ClassBasedValidatorDescriptor.newInstance(ClassBasedValidatorDescriptor.java:65)
    at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager.createAndInitializeValidator(ConstraintValidatorManager.java:184)
    at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager.getInitializedValidator(ConstraintValidatorManager.java:136)
    at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.getInitializedConstraintValidator(ConstraintTree.java:148)
    at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.getInitializedConstraintValidator(ConstraintTree.java:124)
    at org.hibernate.validator.internal.engine.constraintvalidation.SimpleConstraintTree.validateConstraints(SimpleConstraintTree.java:55)
    at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateConstraints(ConstraintTree.java:73)
    at org.hibernate.validator.internal.metadata.core.MetaConstraint.doValidateConstraint(MetaConstraint.java:127)
    at org.hibernate.validator.internal.metadata.core.MetaConstraint.validateConstraint(MetaConstraint.java:120)
    at org.hibernate.validator.internal.engine.ValidatorImpl.validateMetaConstraint(ValidatorImpl.java:533)
    at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForSingleDefaultGroupElement(ValidatorImpl.java:496)
    at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForDefaultGroup(ValidatorImpl.java:465)
    at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForCurrentGroup(ValidatorImpl.java:430)
    at org.hibernate.validator.internal.engine.ValidatorImpl.validateInContext(ValidatorImpl.java:380)
    at org.hibernate.validator.internal.engine.ValidatorImpl.validate(ValidatorImpl.java:169)
    at com.x.x.AccountValidatorTest.shouldDetectDuplicatedEmailAddress(AccountValidatorTest.java:95)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:73)
    at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:83)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)
Caused by: java.lang.InstantiationException: com.x.x.validator.UniqueEmailValidator
    at java.lang.Class.newInstance(Unknown Source)
    at org.hibernate.validator.internal.util.privilegedactions.NewInstance.run(NewInstance.java:40)
    ... 50 more
Caused by: java.lang.NoSuchMethodException: com.x.x.validator.UniqueEmailValidator.<init>()
    at java.lang.Class.getConstructor0(Unknown Source)
    ... 52 more

谢谢。

【问题讨论】:

    标签: java spring validation spring-boot junit


    【解决方案1】:

    实际问题在您提供的堆栈跟踪中很深:

    Caused by: java.lang.NoSuchMethodException: com.x.x.validator.UniqueEmailValidator.<init>()
    at java.lang.Class.getConstructor0(Unknown Source)
    ... 52 more
    

    抛出此错误消息是因为有一些代码试图在没有任何参数的情况下实例化 UniqueEmailValidator 类构造函数。这个问题可以通过给这个类添加一个默认的构造函数来解决:

    public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {
        public UniqueEmailValidator() {
        }
        ...
    }
    

    希望这会有所帮助!

    【讨论】:

    • 嗨,我已经添加了更新堆栈的第一篇文章。添加的默认构造函数并没有完全帮助。
    • 我没有看到更新后的堆栈跟踪更新了问题!
    • @user1778855,您需要了解之前报告的问题已解决,您遇到了新问题。您可能希望接受此答案并针对您的新问题提出新问题!
    • 检查你的逻辑,因为它现在抛出一个NullPointerException
    • 问题比我想象的要多,见stackoverflow.com/questions/26764532/…。这与我面临的情况相似。但是,我的正在单元测试中。我仍在考虑如何在单元测试中将其应用于我的案例。不过还是谢谢。
    【解决方案2】:

    你需要有一个默认的非参数公共构造函数。 如果您需要将 Spring 组件传递给您的验证器,您可能会在测试中遇到问题。

    solved it 拥有一个组件,该组件包含一个服务并有一个提供服务的静态方法。

    【讨论】:

      【解决方案3】:

      有一种方法可以在不添加默认构造函数的情况下设置测试,方法是将验证器作为上下文提供给验证器工厂。您可以通过模拟 ConstraintValidatorFactory 并在请求时返回新的 UniqueEmailValidator 实例来做到这一点:

      private static Validator validator;
      
      @Autowired
      private final AccountService accountService;
      
      @BeforeClass
      public static void setUp() {
          ConstraintValidatorFactory cvf = mock(ConstraintValidatorFactory.class);
          when(cvf.getInstance(UniqueEmailValidator.class)).thenReturn(new UniqueEmailValidator(accountService));
      
          validator = Validation.buildDefaultValidatorFactory()
              .usingContext()
              .constraintValidatorFactory(cvf)
              .getValidator();
      }
      
      ...
      

      【讨论】:

        【解决方案4】:

        我遇到了基本相同的问题,但使用 Mock 测试时,我是这样解决的:

        1. 这是我的自定义约束验证的样子:

        界面:

           @Target(ElementType.FIELD)
           @Retention(RetentionPolicy.RUNTIME)
           @Constraint(validatedBy = UniqueUsernameValidator.class)
           public @interface UniqueUsername {
        
            String message() default "This username is already in use";
        
            Class<?>[] groups() default {};
        
            Class<? extends Payload>[] payload() default {};
        
           }
        

        实施:

            @RequiredArgsConstructor
            public class UniqueUsernameValidator implements ConstraintValidator<UniqueUsername, String> {
        
                private final UserRepository repository;
        
                @Override
                public boolean isValid(String username, ConstraintValidatorContext constraintValidatorContext) {
                    return !repository.existsById(username);
                }
           }
        
        1. @SpringBootTest注释你的测试类

        2. 通过Spring配置设置MockMvc:

          @ExtendWith(MockitoExtension.class)
          @SpringBootTest
          public class RegistrationTest {
          
              @Autowired
              private WebApplicationContext wac;
          
              private MockMvc mockMvc;
          
              @BeforeEach
              void setUp() {
                  this.mockMvc = MockMvcBuilders
                          .webAppContextSetup(wac)
                          .alwaysDo(print())
                          .build();
              }
          
              // your tests
          
          }
          

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2021-10-20
          • 2010-09-25
          • 1970-01-01
          • 2012-12-01
          • 2014-01-30
          • 1970-01-01
          • 2018-12-06
          相关资源
          最近更新 更多