developer747
developer747

Reputation: 15958

Java custom constraints validator fails in unit tests with "No target Validator set" error

I have springboot application which wires up validatorFactoryBean like this

private LocalValidatorFactoryBean validatorFactoryBean;

@Autowired
public void setValidatorFactoryBean(LocalValidatorFactoryBean validatorFactoryBean) {
    this.validatorFactoryBean = validatorFactoryBean;
}

I have custom validators defined on class Car and I run the validations like this.

 Set<ConstraintViolation<Car>> constraintViolations = validatorFactoryBean.validate(myCar);

All of this works fine. But when I try to test this thru unit tests, on the line

validatorFactoryBean.validate(myCar)

I get an error "No target Validator set".

This is how I am setting up validatorFactoryBean in the testng unit test

@InjectMocks
LocalValidatorFactoryBean validatorFactoryBean;

I pass this in to the class I am testing by calling setValidatorFactoryBean setter.

While debugging I realized this validatorFactoryBean is not aware of all the ConstraintValidator implementations in the main code.

How do I make validatorFactoryBean aware of all the ConstraintValidator implementations?

This is how the car constraint validator looks like

public class CarConstraintImpl implements ConstraintValidator<CarConstraint, req> {

    @Inject
    FuelDependency fuelDependency;

    public CarConstraintImpl() {
    }
    @Override
    public boolean isValid(Request req, ConstraintValidatorContext constraintContext) {
        try {
            fuelDependency.check(req);
        } catch (Exception e) {
            //some code
        }
        return true;
    }

}

Upvotes: 0

Views: 2281

Answers (3)

Dimitri Mestdagh
Dimitri Mestdagh

Reputation: 44745

(Option 1) If the LocalValidatorFactoryBean is created by a Spring container, then some additional logic is executed during the afterPropertiesSet() phase.

You could replicate this by calling this method by yourself. For example:

@BeforeEach
void setUp() {
    validator = new LocalValidatorFactoryBean();
    validator.afterPropertiesSet();
}

(Option 2) However, this is kind of a hack, because the default LocalValidatorFactoryBean created by Spring Boot contains extra customizers and a message interpolator. If you don't care about this, it might be cleaner to work with the underlying JSR-380 bean validator in stead. For example:

@BeforeEach
void setUp() {
    ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
    validator = factory.getValidator();
}

Be aware that validator will be a different type, but it also contains a validate() method with the same signature.

(Option 3) If you do care about the additional setup Spring provides, then you could write a Spring integration test. I don't recommend using @SpringBootTest as you would bloat your tests.

All you really need is to include the SpringExtension and the necessary autoconfiguration:

@ExtendWith(SpringExtension.class)
@ImportAutoConfiguration(ValidationAutoConfiguration.class)
public class MyValidationTest {
    @Autowired
    private LocalValidatorFactoryBean validator;
}

Upvotes: 2

devatherock
devatherock

Reputation: 5001

I would also suggest that you use a @SpringBootTest, as described in Ken's answer. But if for some reason you don't want to, a LocalValidatorFactoryBean object initialized using the below code should work for tests as long as all of the custom constraint validators have a default, no-args constructor:

LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();

Upvotes: 1

Ken Chan
Ken Chan

Reputation: 90517

@InjectMocks is just a mockito stuff and never understand spring. It just like you create a LocalValidatorFactoryBean manually without any spring integration to it.

To configure LocalValidatorFactoryBean properly , you can do it by using @SpringBootTest with the @ImportAutoConfiguration to auto-configure just the bean validation stuff.

@SpringBootTest
public class ValidationTest{


    @Configuration
    @ImportAutoConfiguration(ValidationAutoConfiguration.class)                
    @ComponentScan(basePackages = { "my.app.root.package" })
    public static class Config {

    }

    @Autowired
    LocalValidatorFactoryBean validatorFactoryBean;
    

    @Test
    void test(){
    
    }
    
}

Upvotes: 2

Related Questions