java - validator 类内的依赖项未在单元测试中初始化

标签 java spring unit-testing validation spring-boot

来自Spring unit test issue with Validator部分问题已得到解决。

我正在尝试对 Validator 类执行单元测试,该类在类内部具有依赖项。

@NoArgsConstructor
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {

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

这是 UniqueEmailValidator.java:47 的堆栈 return !this.accountService.findByEmail(email).isPresent();

javax.validation.ValidationException: HV000028: Unexpected exception during isValid call.
    at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateSingleConstraint(ConstraintTree.java:177)
    at org.hibernate.validator.internal.engine.constraintvalidation.SimpleConstraintTree.validateConstraints(SimpleConstraintTree.java:68)
    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.NullPointerException
    at com.x.x.validator.UniqueEmailValidator.isValid(UniqueEmailValidator.java:47)
    at com.x.x.validator.UniqueEmailValidator.isValid(UniqueEmailValidator.java:1)
    at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateSingleConstraint(ConstraintTree.java:171)
    ... 43 more

我的问题是,如果 validator 在单元测试中是这样初始化的,我如何在单元测试期间提供 accountService 的注入(inject)?在我看来,accountService 没有被注入(inject)或其他什么,因此 NPE。

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

}

最佳答案

我知道这个问题很老了,但我只是花了一天时间没有找到问题的真正解决方案和完整的解释。

所以我们开始吧。我将尽可能详细地说明事情。

在此过程结束时,您应该:

  • 注解接口(interface):UniqueEmail
  • 自定义约束 validator :UniqueEmailValidator
  • validator 测试助手配置:ValidatorTestHelperConfig
  • validator 测试助手(这是为了清楚起见):ValidatorTestHelper
  • 您的测试类:AccountValidatorTest

这是代码:

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

在 validator 中,您不需要放置@NoArgsConstructor。

public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {

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

接下来,您需要配置类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

@Configuration
public class ValidatorTestHelperConfiguration {

    @Bean
    public LocalValidatorFactoryBean validator() {
        return new LocalValidatorFactoryBean();
    }
}

为了更干净的测试,我们编写了一个测试助手(我只放置了相关的导入)

import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = { ValidatorTestHelperConfiguration.class })
public abstract class ValidatorTestHelper {

    @Autowired
    protected Validator validator;

    protected List<String> getPropertyPaths(Set<? extends ConstraintViolation<?>> violations) {
        return violations.stream().map(ConstraintViolation::getPropertyPath).map(Object::toString).collect(Collectors.toList());
    }

    protected List<String> getMessageTemplate(Set<? extends ConstraintViolation<?>> violations) {
        return violations.stream().map(ConstraintViolation::getMessageTemplate).map(msg -> msg.replaceAll("([{}])", "")).collect(Collectors.toList());
    }
}

这是最后一 block ,你的测试。我正在使用 JUnit5,因此使用 @ExtendWith (只是让你知道,这行并不是强制性的)。 请注意,我在这里扩展了助手。

import org.junit.jupiter.api.Test;
import javax.validation.ConstraintViolation;
import org.springframework.boot.test.mock.mockito.MockBean;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
public class AccountValidatorTest extends ValidatorTestHelper {

    @MockBean
    private AccountService accountService;

    @Test
    public void shouldDetectDuplicatedEmailAddress() {
        User user = new User(); 
        // other things

        Optional<User> userOptional = Optional.of(mock(User.class));
        when(this.accountService.findByEmail(user.getEmail())).thenReturn(userOptional);


        Set<ConstraintViolation<AccountRegistrationForm>> violations = validator.validate(user);

        assertEquals(1, violations.size());

        assertThat(getMessageTemplate(validate)).containsOnlyElementsOf(asList(
            "{com.x.x.validator.UniqueEmail.message}")
        );

        assertThat(getPropertyPaths(validate)).containsExactlyInAnyOrder(
            "accountRegistrationForm.email"
        );
   }
}

就是这样,希望对您有所帮助。

关于java - validator 类内的依赖项未在单元测试中初始化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50119167/

相关文章:

java - 如何获取包含在我的源目录中的文件?

java - JavaFX 中 Spring Autowiring 的空指针异常

java - Spring Data JPA 保存方法在 Java 8 功能图中的有效性

java - 如何在 JUnit 测试用例中处理 wait() 方法调用?

java - 加载驱动类 com.mysql.jdbc.Driver 失败

java - 我是否也应该在 spring 中为子方法添加 @Transactional 注释?

java - 读取 URL 的前 5 行,然后以相反的顺序打印

JavaMailSenderImpl 不使用邮件 session 中的 "mail.from"属性

c# - 在测试中使用已实现的方法是好主意还是坏主意?

asp.net - 这是测试演示者的正确方法吗