I’m trying to test that my beans have correct validation annotations. I’m using spring-boot. Here is an example test case:
package com.example.sandbox; import static org.assertj.core.api.Assertions.assertThatThrownBy; import javax.validation.ConstraintViolationException; import javax.validation.Valid; import javax.validation.constraints.NotNull; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.validation.annotation.Validated; @SpringBootTest class ValidationTest { @Test void testConstructor() { TestedBean bean = new TestedBean(null); assertThatThrownBy(() -> checkIfValidated(bean)).isInstanceOf(ConstraintViolationException.class); } @Test void testSetter() { TestedBean bean = new TestedBean(null); assertThatThrownBy(() -> bean.setSomeProperty(null)).isInstanceOf(ConstraintViolationException.class); } private void checkIfValidated(@Valid TestedBean bean) { } @Validated class TestedBean { @NotNull private String someProperty; public TestedBean(String someProperty) { super(); this.someProperty = someProperty; } public String getSomeProperty() { return someProperty; } public void setSomeProperty(@NotNull String someProperty) { this.someProperty = someProperty; } } }
I expect the call to checkIfvalidated()
and to setSomeProperty(null)
to raise a ConstraintViolationException
, and the tests to pass, but they both fail with:
java.lang.AssertionError: Expecting code to raise a throwable. at com.example.sandbox.ValidationTest.test(ValidationTest.java:20) ...
My pom.xml:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.0</version> <relativePath /> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>com.example.springbootsandbox</artifactId> <version>0.0</version> <name>SpringBootSandbox</name> <description>Sandbox for Spring Boot</description> <properties> <java.version>11</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>6.1.5.Final</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Why is there no ConstraintViolationException
raised here? The bean property has a @NotNull
annotation, the bean itself is @Validated
and the method signature requires a @Valid
bean.
Is there a simple way to have that exception raised in the context of my test class?
When I use validation annotations on method signatures for a service interface, everything works as expected. I don’t understand where is the difference.
Service interface:
package com.example.sandbox; import javax.validation.constraints.NotNull; import org.springframework.validation.annotation.Validated; @Validated public interface IService { public void setValue(@NotNull String value); }
Service implementation:
package com.example.sandbox; import org.springframework.stereotype.Service; @Service public class SomeService implements IService { @Override public void setValue(String value) { // Do nothing } }
Test case:
package com.example.sandbox; import static org.assertj.core.api.Assertions.assertThatThrownBy; import javax.validation.ConstraintViolationException; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class SomeServiceTests { @Autowired IService service; @Test void testSetValue() { assertThatThrownBy(() -> service.setValue(null)).isInstanceOf(ConstraintViolationException.class); } }
==> The test passes.
Working code according to the given answer:
The test class:
@SpringBootTest class ValidationTest { @Autowired private Validator validator; // Using the default validator to test property annotations @Autowired private TestedBeanService service; // Using a service to test method annotations @Test void testPropertyAnnotations() { TestedBean bean = new TestedBean(null); Set<ConstraintViolation<TestedBean>> violations = validator.validate(bean); assertThat(violations).isNotEmpty(); } @Test void testMethodAnnotations() { TestedBean bean = new TestedBean(null); assertThatThrownBy(() -> service.setBeanProperty(bean, null)).isInstanceOf(ConstraintViolationException.class); } }
The tested bean:
@Validated class TestedBean { @NotNull private String someProperty; public TestedBean(String someProperty) { super(); this.someProperty = someProperty; } public String getSomeProperty() { return someProperty; } public void setSomeProperty(String someProperty) { // No more annotation on setter this.someProperty = someProperty; } }
The service interface:
@Validated public interface TestedBeanService { // method annotation on the interface method void setBeanProperty(TestedBean bean, @NotNull String someProperty); }
The service implementation:
@Service public class TestedBeanServiceImpl implements TestedBeanService { @Override public void setBeanProperty(TestedBean bean, String someProperty) { bean.setSomeProperty(someProperty); } }
Advertisement
Answer
Why is there no
ConstraintViolationException
raised here? The bean property has a@NotNull
annotation, the bean itself is@Validated
and the method signature requires a@Valid
bean.
Annotations by themselves do not mean anything, they should be processed in some way. In this case the @Validated
annotation is processed by Spring for its beans. The test is not a Spring bean, so the framework does not look at the annotations related to valdidation, hence no exception.
Even if the test were a Spring Bean, the approach may not work out of the box. See this question for details.
Is there a simple way to have that exception raised in the context of my test class?
Take a look at this question
When I use validation annotations on method signatures for a service interface, everything works as expected. I don’t understand where is the difference.
This happens because the service
is a Spring bean, but test is not. When a method on the service
is invoked, it gets intercepted by MethodValidationInterceptor
, which is not the case for test