Reputation: 669
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.
@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? 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.
Upvotes: 4
Views: 5372
Reputation: 485
I faced basically the same issue but with Mock testing and here's how I solved it:
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);
}
}
Annotate your test class with @SpringBootTest
Set up MockMvc through Spring configuration:
@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
}
Upvotes: 1
Reputation: 9537
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:
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();
}
...
Upvotes: 1
Reputation: 13083
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.
I solved it by having a component which holds a service and has a static method to give the service.
Upvotes: 1
Reputation: 4647
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. 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!
Upvotes: 5