来自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/