简体   繁体   English

验证器的 Spring 单元测试问题

[英]Spring unit test issue with Validator

I am trying to write unit test for a validator class that I have.我正在尝试为我拥有的验证器类编写单元测试。 So within my UniqueEmailValidator class, I injected a @Service component to check if it exist.所以在我的 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 {};
}

And I tried to write unit test for this constraint validator.我尝试为这个约束验证器编写单元测试。

@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());
    }

}

How do I initialize the AccountService within the Validator class?如何在 Validator 类中初始化 AccountService? It seem like it wasn't injected, hence, the null exception.似乎它没有被注入,因此,空异常。 Here's the trace.这是踪迹。

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

Thanks.谢谢。

The actual problem is deep down in the stack-trace that you've provided:实际问题深藏在您提供的堆栈跟踪中:

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

This error message is thrown because there is some code that is trying to instantiate the UniqueEmailValidator class constructor without any parameters.抛出此错误消息是因为有一些代码试图在没有任何参数的情况下实例化UniqueEmailValidator类构造函数。 The problem will be resolved by adding a default constructor to this class:该问题将通过向此类添加默认构造函数来解决:

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

Hope this helps!希望这可以帮助!

You need to have a default non argument public constructor.您需要有一个默认的非参数公共构造函数。 If you need to pass a Spring component into your validator, you may face a problem in test.如果需要将 Spring 组件传递给验证器,则可能会在测试中遇到问题。

I solved it by having a component which holds a service and has a static method to give the service.我通过拥有一个包含服务并具有提供服务的静态方法的组件来解决它

I faced basically the same issue but with Mock testing and here's how I solved it:我遇到了基本相同的问题,但是通过模拟测试,这是我解决的方法:

  1. Here is how my custom constraint validation looks like:这是我的自定义约束验证的样子:

Interface:界面:

   @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 {};

   }

Implementation:执行:

    @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. Annotate your test class with @SpringBootTest使用@SpringBootTest注释您的测试类

  2. Set up MockMvc through Spring configuration:通过 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 }

There is a way to setup the test without adding a default constructor by providing your validator as context to the validator factory.有一种方法可以在不添加默认构造函数的情况下设置测试,方法是将验证器作为验证器工厂的上下文。 You can do this by mocking a ConstraintValidatorFactory and returning a new UniqueEmailValidator instance when requested:你可以通过模拟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();
}

...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM