How to test validation annotations of a class using JUnit? - java

I need to test the validation annotations but it looks like they do not work. I am not sure if the JUnit is also correct. Currently, the test will be passed but as you can see the specified email address is wrong.
JUnit
public static void testContactSuccess() {
Contact contact = new Contact();
contact.setEmail("Jackyahoo.com");
contact.setName("Jack");
System.err.println(contact);
}
Class to be tested
public class Contact {
#NotNull
#Size(min = 1, max = 10)
String name;
#NotNull
#Pattern(regexp="[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\."
+"[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*#"
+"(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?",
message="{invalid.email}")
String email;
#Digits(fraction = 0, integer = 10)
#Size(min = 10, max = 10)
String phone;
getters and setters
}

The other answer saying that "the annotations do not do anything by themselves, you need to use a Validator to process the object" is correct, however, the answer lacks working instructions on how to do it using a Validator instance, which for me was what I really wanted.
Hibernate-validator is the reference implementation of such a validator. You can use it quite cleanly like this:
import static org.junit.Assert.assertFalse;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class ContactValidationTest {
private Validator validator;
#Before
public void setUp() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
#Test
public void testContactSuccess() {
// I'd name the test to something like
// invalidEmailShouldFailValidation()
Contact contact = new Contact();
contact.setEmail("Jackyahoo.com");
contact.setName("Jack");
Set<ConstraintViolation<Contact>> violations = validator.validate(contact);
assertFalse(violations.isEmpty());
}
}
This assumes you have validator implementation and junit as dependencies.
Example of dependencies using Maven pom:
<dependency>
<groupId>org.hibernate</groupId>
<version>5.2.4.Final</version>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>

A simple way to test validation annotations using javax:
Declare the Validator at Class level:
private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Then in your test simply call it on the object you require validation on, with what exception you are validating:
Set<TheViolation<TheClassYouAreValidating> violations = validator.validate(theInstanceOfTheClassYouAreValidating);
Then simply assert the number of expected violations:
assertThat(violations.size()).isEqualTo(1);
You will need to add this to your dependencies (gradle):
compile group: 'javax.validation', name: 'validation-api', version: '2.0.1.Final'

The annotations do not do anything by themselves, you need to use a Validator to process the object.
Your test needs to run some code like this
Configuration<?> configuration = Validation
.byDefaultProvider()
.providerResolver( new MyResolverStrategy() ) // <== this is where is gets tricky
.configure();
ValidatorFactory factory = configuration.buildValidatorFactory();
Contact contact = new Contact();
contact.setEmail("Jackyahoo.com");
contact.setName("Jack");
factory.getValidator().validate(contact); <== this normally gets run in the background by whatever framework you are using
However, the difficulty you face here are these are all interfaces, you will need implementations to be able to test. You could implement it yourself or find one to use.
However the question you want to ask yourself is what are you trying to test? That the hibernate validator works the way it should? or that your regex is correct?
If this was me I would assume that the Validator works(ie someone else tested that) and focus on the regex. Which would involve a bit of reflection
public void emailRegex(String email,boolean validates){
Field field = Contact.class.getDeclaredField("email");
javax.validation.constraints.Pattern[] annotations = field.getAnnotationsByType(javax.validation.constraints.Pattern.class);
assertEquals(email.matches(annotations[0].regexp()),validates);
}
then you can define your testMethods which are actual unit tests
#Test
public void testInvalidEmail() throws NoSuchFieldException {
emailRegex("Jackyahoo.com", false);
}
#Test
public void testValidEmail() throws NoSuchFieldException {
emailRegex("jack#yahoo.com", true);
}
#Test
public void testNoUpperCase() throws NoSuchFieldException {
emailRegex("Jack#yahoo.com", false);
}

First thanks #Eis for the answer, it helped me. It's a good way to fail the test, but I wanted a bit more "life-like" behaviour. At runtime an exception would be thrown so I came up with this:
/**
* Simulates the behaviour of bean-validation e.g. #NotNull
*/
private void validateBean(Object bean) throws AssertionError {
Optional<ConstraintViolation<Object>> violation = validator.validate(bean).stream().findFirst();
if (violation.isPresent()) {
throw new ValidationException(violation.get().getMessage());
}
}
Have an entity with validation:
#Data
public class MyEntity {
#NotBlank(message = "Name cannot be empty!")
private String name;
}
In a test you can pass an instance with invalid attributes and expect an exception:
private Validator validator;
#Before
public void setUp() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
#Test(expected = ValidationException.class)
public void testValidationWhenNoNameThenThrowException() {
validateBean(new Entity.setName(""));
}

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;
public class ValidationTest {
private Validator validator;
#Before
public void init() {
ValidatorFactory vf = Validation.buildDefaultValidatorFactory();
this.validator = vf.getValidator();
}
#Test
public void prereqsMet() {
Workshop validWorkshop = new Workshop(2, 2, true, 3);
Set<ConstraintViolation<Workshop>> violations = this.validator.validate(validWorkshop);
assertTrue(violations.isEmpty());
}
}
Strictly speaking it is not a unit test, rather an Integration Test. In Unit Test you would like to test the validator logic only, without any dependencies to the SPI.
https://www.adam-bien.com/roller/abien/entry/unit_integration_testing_the_bean

Here my way to unit test my objects with fields annotated with some javax.validation.constraints constraints.
I will give an example with Java 8, JPA entity, Spring Boot and JUnit 5 but the overall idea is the same whatever the context and the frameworks :
We have a nominal scenario where all fields are correctly valued and generally multiple error scenarios where one or more fields are not correctly valued.
Testing field validation is not a particularly hard thing.
But as we have many fields to validate, the tests may become more complex, we can forget some cases, introducing side effects in tests between two cases to validate or simply introduce duplication.
I will give my mind about how to avoid that.
In the OP code, we will suppose that the 3 fields have a NotNull constraint. I think that under 3 distinct constraints, the pattern and its value are less visible.
I wrote first a unit test for the nominal scenario :
import org.junit.jupiter.api.Test;
#Test
public void persist() throws Exception {
Contact contact = createValidContact();
// action
contactRepository.save(contact);
entityManager.flush();
entityManager.clear();
// assertion on the id for example
...
}
I extract the code to create a valid contact into a method as it will be helpful for no nominal cases :
private Contact createValidContact(){
Contact contact = new Contact();
contact.setEmail("Jackyahoo.com");
contact.setName("Jack");
contact.setPhone("33999999");
return contact;
}
Now I write a #parameterizedTest with as fixture source a #MethodSource method :
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import javax.validation.ConstraintViolationException;
#ParameterizedTest
#MethodSource("persist_fails_with_constraintViolation_fixture")
void persist_fails_with_constraintViolation(Contact contact ) {
assertThrows(ConstraintViolationException.class, () -> {
contactRepository.save(contact);
entityManager.flush();
});
}
To compile/run #parameterizedTest, think of adding the required dependency that is not included in the junit-jupiter-api dependency :
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
In the fixture method to create invalid contacts, the idea is simple. For each case, I create a new valid contact object and I set incorrectly only the field to validate concerned to.
In this way, I ensure that no side effect between cases are present and that each case provokes itself the expected validation exception as without the field set the valid contact was successful persisted.
private static Stream<Contact> persist_fails_with_constraintViolation_fixture() {
Contact contactWithNullName = createValidContact();
contactWithNullName.setName(null);
Contact contactWithNullEmail = createValidContact();
contactWithNullEmail.setEmail(null);
Contact contactWithNullPhone = createValidContact();
contactWithNullPhone.setPhone(null);
return Stream.of(contactWithNullName, contactWithNullEmail, contactWithNullPhone);
}
Here is the full test code :
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import javax.validation.ConstraintViolationException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.context.junit.jupiter.SpringExtension;
#DataJpaTest
#ExtendWith(SpringExtension.class)
public class ContactRepositoryTest {
#Autowired
private TestEntityManager entityManager;
#Autowired
private ContactRepository contactRepository;
#BeforeEach
public void setup() {
entityManager.clear();
}
#Test
public void persist() throws Exception {
Contact contact = createValidContact();
// action
contactRepository.save(contact);
entityManager.flush();
entityManager.clear();
// assertion on the id for example
...
}
#ParameterizedTest
#MethodSource("persist_fails_with_constraintViolation_fixture")
void persist_fails_with_constraintViolation(Contact contact ) {
assertThrows(ConstraintViolationException.class, () -> {
contactRepository.save(contact);
entityManager.flush();
});
}
private static Stream<Contact> persist_fails_with_constraintViolation_fixture() {
Contact contactWithNullName = createValidContact();
contactWithNullName.setName(null);
Contact contactWithNullEmail = createValidContact();
contactWithNullEmail.setEmail(null);
Contact contactWithNullPhone = createValidContact();
contactWithNullPhone.setPhone(null);
return Stream.of(contactWithNullName, contactWithNullEmail, contactWithNullPhone);
}
}

There are 2 things that you need to check:
The validation rules are configured correctly
The validation rules can be checked the way others advise - by creating a validator object and invoking it manually:
Validator validator = Validation.buildDefaultValidatorFactory().getValidator()
Set violations = validator.validate(contact);
assertFalse(violations.isEmpty());
With this you should check all the possible cases - there could be dozens of them (and in this case there should be dozens of them).
The validation is triggered by the frameworks
In your case you check it with Hibernate, therefore there should be a test that initializes it and triggers some Hibernate operations. Note that for this you need to check only one failing rule for one single field - this will be enough. You don't need to check all the rules from again. Example could be:
#Test(expected = ConstraintViolationException.class)
public void validationIsInvokedBeforeSavingContact() {
Contact contact = Contact.random();
contact.setEmail(invalidEmail());
contactsDao.save(contact)
session.flush(); // or entityManager.flush();
}
NB: don't forget to trigger flush(). If you work with UUIDs or sequences as an ID generation strategy, then INSERT is not going to be flushed when you save() - it's going to be postponed until later.
This all is a part of how to build a Test Pyramid - you can find more details here.

such as:
public class Test {
#Autowired
private Validator validator;
public void testContactSuccess() {
Contact contact = new Contact();
contact.setEmail("Jackyahoo.com");
contact.setName("Jack");
System.err.println(contact);
Set<ConstraintViolation<Contact>> violations = validator.validate(contact);
assertTrue(violations.isEmpty());
}
}
and you also need add bean autowired in your context.xml, such as:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
</bean>

If you try using new versions of the validator but land on that thread (like me), you will start getting tons of wired exceptions. So should have in mind that to do test with Hibernate 7+
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>7.0.2.Final</version>
<scope>test</scope>
</dependency>
should be sure that you are NOT using
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
but switched to
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.0.1</version>
</dependency>
and have
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
<version>4.0.2</version>
<scope>test</scope>
</dependency>

For those with Spring Boot with Spring-Data-JPA you just need to autowire the validator
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.validation.Validator;
import static org.assertj.core.api.Assertions.assertThat;
#SpringBootTest
class ValidatorTest {
#Autowired
private Validator validator;
#Test
void ensureValidatorIsLoaded() {
assertThat(validator).isNotNull();
}
#Test
void failValidate() {
final var violations = validator.validate(new StartRequest());
assertThat(violations).isNotEmpty();
}
#Test
void passValidate() {
final var startRequest = StartRequest.builder()
.contentType("foo/bar")
.contentMd5Hash("abcdef1234567890abcdef1234567890")
.category("Pc")
.contentLength(55)
.siteId("ca1")
.desiredExpiration(55)
.build();
final var violations = validator.validate(startRequest);
assertThat(violations).isEmpty();
}
}

I think validations would work after calling predefined methods which is usually done by the containers mostly not immediately after calling setters of the object. From the documentation link you shared:
>
By default, the Persistence provider will automatically perform validation on entities with persistent fields or properties annotated with Bean Validation constraints immediately after the PrePersist, PreUpdate, and PreRemove lifecycle events.

Related

Testing custom JsonDeserializer in Jackson / SpringBoot

I am trying to write a unit test to a custom deserializer that is instantiated using a constructor with an #Autowired parameter and my entity marked with #JsonDeserialize. It works fine in my integration tests where a MockMvc brings up spring serverside.
However with tests where objectMapper.readValue(...) is being called, a new instance of deserializer using default constructor with no parameters is instantiated. Even though
#Bean
public MyDeserializer deserializer(ExternalObject externalObject)
instantiates wired version of deserializer, real call is still passed to empty constructor and context is not being filled up.
I tried manually instantiating of a deserializer instance and registering it in ObjectMapper, but it only works if I remove #JsonDeserialize from my entity class (and it breaks my integration tests even if I do the same in my #Configuration class.) - looks related to this: https://github.com/FasterXML/jackson-databind/issues/1300
I can still test the deserializer behavior calling deserializer.deserialize(...) directly, but this approach doesn't work for me in tests that are not Deserializer's unit tests...
UPD: working code below
import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
import com.github.tomakehurst.wiremock.common.Json;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.json.JsonTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
import java.io.IOException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
#JsonTest
#RunWith(SpringRunner.class)
public class JacksonInjectExample {
private static final String JSON = "{\"field1\":\"value1\", \"field2\":123}";
public static class ExternalObject {
#Override
public String toString() {
return "MyExternalObject";
}
}
#JsonDeserialize(using = MyDeserializer.class)
public static class MyEntity {
public String field1;
public String field2;
public String name;
public MyEntity(ExternalObject eo) {
name = eo.toString();
}
#Override
public String toString() {
return name;
}
}
#Component
public static class MyDeserializer extends JsonDeserializer<MyEntity> {
#Autowired
private ExternalObject external;
public MyDeserializer() {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
}
public MyDeserializer(#JacksonInject final ExternalObject external) {
this.external = external;
}
#Override
public MyEntity deserialize(JsonParser p, DeserializationContext ctxt) throws IOException,
JsonProcessingException {
return new MyEntity(external);
}
}
#Configuration
public static class TestConfiguration {
#Bean
public ExternalObject externalObject() {
return new ExternalObject();
}
#Bean
public MyDeserializer deserializer(ExternalObject externalObject) {
return new MyDeserializer(externalObject);
}
}
#Test
public void main() throws IOException {
HandlerInstantiator hi = mock(HandlerInstantiator.class);
MyDeserializer deserializer = new MyDeserializer();
deserializer.external = new ExternalObject();
doReturn(deserializer).when(hi).deserializerInstance(any(), any(), eq(MyDeserializer.class));
final ObjectMapper mapper = Json.getObjectMapper();
mapper.setHandlerInstantiator(hi);
final MyEntity entity = mapper.readValue(JSON, MyEntity.class);
Assert.assertEquals("MyExternalObject", entity.name);
}
}
I don't know how to set this particularly using Jackson injection, but you can test it using spring Json tests. I think this method is closer to the real scenario and much more simplier. Spring will load only related to serialization/deserialization beans, thus you have to provide only custom beans or mocks instead them.
#JsonTest
public class JacksonInjectExample {
private static final String JSON = "{\"field1\":\"value1\", \"field2\":123}";
#Autowired
private JacksonTester<MyEntity> jacksonTester;
#Configuration
public static class TestConfiguration {
#Bean
public ExternalObject externalObject() {
return new ExternalObject();
}
}
#Test
public void test() throws IOException {
MyEntity result = jacksonTester.parseObject(JSON);
assertThat(result.getName()).isEqualTo("MyExternalObject");
}
If you would like to use mocks use following snippet:
#MockBean
private ExternalObject externalObject;
#Test
public void test() throws IOException {
when(externalObject.toString()).thenReturn("Any string");
MyEntity result = jacksonTester.parseObject(JSON);
assertThat(result.getName()).isEqualTo("Any string");
}
Very interesting question, it made me wonder how autowiring into jackson deserializers actually works in a spring application. The jackson facility that is used seems to be the HandlerInstantiator interface, which is configured by spring to the SpringHandlerInstantiator implementation, which just looks up the class in the application context.
So in theory you could setup an ObjectMapper in your unit test with your own (mocked) HandlerInstantiator, returning a prepared instance from deserializerInstance(). It seems to be fine to return null for other methods or when the class parameter does not match, this will cause jackson to create the instance on its own.
However, I do not think this is a good way to unit test deserialization logic, as the ObjectMapper setup is necessarily different from what is used during actual application execution. Using the JsonTest annotation as suggested in Anton's answer would be a much better approach, as you are getting the same json configuration that would be used during runtime.
Unit tests should not depend upon or invoke other major classes or frameworks. This is especially true if there are also integration or acceptance tests covering the functioning of the application with a particular set of dependencies as you describe. So it would be best to write the unit test so that it has a single class as its subject i.e. calling deserializer.deserialize(...) directly.
In this case a unit test should consist of instanciating a MyDeserializer with a mocked or stubbed ExternalObject, then testing that its deserialize() method returns a MyEntity correctly for different states of the JsonParser and DeserializationContext arguments. Mockito is really good for setting up mock dependencies!
By using an ObjectMapper in the unit test, quite a lot of code from the Jackson framework is also being invoked in each run - so the test is not verifying the contract of MyDeserializer, it is verifying the behaviour of the combination of MyDeserializer and a particular release of Jackson. If there is a failure of the test it won't be immediatly clear which of all the components involved is at fault. And because setting up the environment of the two frameworks together is more difficult the test will prove brittle over time and fail more often due to issues with the setup in the test class.
The Jackson framework is responsible for writing unit tests of ObjectMapper.readValue and constructors using #JacksonInject. For the 'other unit tests that are not Deserializer's unit tests' - it would be best to mock/stub the MyDeserializer (or other dependencies) for that test. That way the other class's logic is being isolated from the logic in MyDeserializer - and the other class's contracts can be verified without being qualified by the behaviour of code outside of the unit under test.

#Transactional service methods rollback hibernate changes

I have a method inside a #Service class which calls two different methods in two different #Service classes. These two different methods save two entities inside the database (through hibernate) and they both may throw some exceptions.
I would like that if an exception is thrown, independently from which #Service method, all the changes are rolled back. So all the entities created inside the database are deleted.
//entities
#Entity
public class ObjectB{
#Id
private long id;
...
}
#Entity
public class ObjectC{
#Id
private long id;
...
}
//servicies
#Service
#Transactional
public class ClassA{
#Autowired
private ClassB classB;
#Autowired
private ClassC classC;
public void methodA(){
classB.insertB(new ObjectB());
classC.insertC(new ObjectC());
}
}
#Service
#Transactional
public class ClassB{
#Autowired
private RepositoryB repositoryB;
public void insertB(ObjectB b){
repositoryB.save(b);
}
}
#Service
#Transactional
public class ClassC{
#Autowired
private RepositoryC repositoryC;
public void insertC(ObjectC c){
repositoryC.save(c);
}
}
//repositories
#Repository
public interface RepositoryB extends CrudRepository<ObjectB, String>{
}
#Repository
public interface RepositoryC extends CrudRepository<ObjectC, String>{
}
I would like that methodA of ClassA, once an exception has been thrown from either methodB or methodC, it rollbacks all the changes inside the database.
But it doesn't do that. All the changes remains after the exception...
What am I missing?
What should I add in order to make it work as I want?
I'm using Spring Boot 2.0.6!
I haven't configured anything in particular to make the transactions work!
EDIT 1
This is my main class if it can help:
#SpringBootApplication
public class JobWebappApplication extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(JobWebappApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(JobWebappApplication.class, args);
}
}
When an exception is thrown this is what I see in the console:
Completing transaction for [com.example.ClassB.insertB]
Retrieved value [org.springframework.orm.jpa.EntityManagerHolder#31d4fbf4] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#df9d400] bound to thread [http-nio-8080-exec-7]
Retrieved value [org.springframework.jdbc.datasource.ConnectionHolder#1d1ad46b] for key [HikariDataSource (HikariPool-1)] bound to thread [http-nio-8080-exec-7]
Getting transaction for [com.example.ClassC.insertC]
Completing transaction for [com.example.ClassC.insertC] after exception: java.lang.RuntimeException: runtime exception!
Applying rules to determine whether transaction should rollback on java.lang.RuntimeException: runtime exception!
Winning rollback rule is: null
No relevant rollback rule found: applying default rules
Completing transaction for [com.example.ClassA.methodA] after exception: java.lang.RuntimeException: runtime exception!
Applying rules to determine whether transaction should rollback on java.lang.RuntimeException: runtime exception!
Winning rollback rule is: null
No relevant rollback rule found: applying default rules
Clearing transaction synchronization
Removed value [org.springframework.jdbc.datasource.ConnectionHolder#1d1ad46b] for key [HikariDataSource (HikariPool-1)] from thread [http-nio-8080-exec-7]
Removed value [org.springframework.orm.jpa.EntityManagerHolder#31d4fbf4] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#df9d400] from thread [http-nio-8080-exec-7]
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: runtime exception!] with root cause
It seems that each time it calls a method it creates a new transaction! Is without rolling back anything after RuntimeException occurs!
EDIT 2
This is the pom.xml dependencies file:
<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-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.10.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.5</version>
</dependency>
</dependencies>
This is the application.properties file:
spring.datasource.url=jdbc:mysql://localhost:3306/exampleDB?useSSL=false
spring.datasource.username=root
spring.datasource.password=password
spring.jpa.show-sql=true
logging.level.org.springframework.transaction=TRACE
spring.jpa.database=MYSQL
spring.jpa.hibernate.ddl-auto=update
spring.datasource.driver.class=com.mysql.jdbc.Driver
spring.jpa.properties.hibernate.locationId.new_generator_mappings=false
SOLUTION
Thanks to #M.Deinum I found the solution!
I was using a wrong database engine (MyISAM), which does not support transaction! So I changed the table engine type with "InnoDB" which supports transactions. What I did is this:
I added this property inside application.properties file, inorder to tell to JPA which was the engine type it should use to "manipulate" the tables:
spring.jpa.properties.hibernate.dialect =
org.hibernate.dialect.MySQL5InnoDBDialect
I dropped all the existing tables (with the wrong engine type) inside my DB and I let JPA to recreate all of them with the right engine (InnoDB).
Now all the RuntimeExceptions thrown make the transaction to rollback all the changes done within it.
ALERT: I noticed that if an exception which is not a subclass of RuntimeException is thrown, no rollback is applied and all the changes already done remain inside the database.
What you are trying to achieve should work out of the box. Check your spring configuration.
Make sure you created TransactionManager bean and make sure you placed #EnableTransactionManagement annotation on some of your spring #Configurations. This annotation are responsible for registering the necessary Spring components that power annotation-driven transaction management, such as the TransactionInterceptor and the proxy- or AspectJ-based advice that weave the interceptor into the call stack when #Transactional methods are invoked.
See the linked documentation.
If you are using spring-boot it should automatically add this annotation for you if you have PlatformTransactionManager class on classpath.
Also, please note that checked exceptions does not trigger a rollback of the transaction. Only runtime exceptions and errors trigger a rollback. You can, of course, configure this behavior with the rollbackFor and noRollbackFor annotation parameters.
Edit
As you clarified that you are using spring-boot, the answer is: all should work without any configuration.
Here is minimal 100% working example for spring-boot version 2.1.3.RELEASE (but should work with any version ofc):
Dependencies:
compile('org.springframework.boot:spring-boot-starter-data-jpa')
runtimeOnly('com.h2database:h2') // or any other SQL DB supported by Hibernate
compileOnly('org.projectlombok:lombok') // for getters, setters, toString
User entity:
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
#Entity
#Getter
#Setter
#ToString
public class User {
#Id
#GeneratedValue
private Integer id;
private String name;
}
Book entity:
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
#Entity
#Getter
#Setter
#ToString
public class Book {
#Id
#GeneratedValue
private Integer id;
#ManyToOne
private User author;
private String title;
}
User repository:
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Integer> {
}
Book repository:
import org.springframework.data.jpa.repository.JpaRepository;
public interface BookRepository extends JpaRepository<Book, Integer> {
}
User service:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
#Transactional
#Component
public class UserService {
#Autowired
private UserRepository userRepository;
public User saveUser(User user) {
// return userRepository.save(user);
userRepository.save(user);
throw new RuntimeException("User not saved");
}
public List<User> findAll() {
return userRepository.findAll();
}
}
Book service:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
#Transactional
#Component
public class BookService {
#Autowired
private BookRepository bookRepository;
public Book saveBook(Book book) {
return bookRepository.save(book);
}
public List<Book> findAll() {
return bookRepository.findAll();
}
}
Composite service:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
#Transactional
#Component
public class CompositeService {
#Autowired
private UserService userService;
#Autowired
private BookService bookService;
public void saveUserAndBook() {
User user = new User();
user.setName("John Smith");
user = userService.saveUser(user);
Book book = new Book();
book.setAuthor(user);
book.setTitle("Mr Robot");
bookService.saveBook(book);
}
}
Main:
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Bean;
#SpringBootApplication
public class JpaMain {
public static void main(String[] args) {
new SpringApplicationBuilder(JpaMain.class)
.web(WebApplicationType.NONE)
.properties("logging.level.org.springframework.transaction=TRACE")
.run(args);
}
#Bean
public CommandLineRunner run(CompositeService compositeService, UserService userService, BookService bookService) {
return args -> {
try {
compositeService.saveUserAndBook();
} catch (RuntimeException e) {
System.err.println("Exception: " + e);
}
System.out.println("All users: " + userService.findAll());
System.out.println("All books: " + bookService.findAll());
};
}
}
If you run the main method you should see that no books or users found in DB. The transaction is rolled back. If you remove the throw new RuntimeException("User not saved") line from UserService, both entities will be saved fine.
Also you should see the logs of org.springframework.transaction package on TRACE level, where for instance you will see:
Getting transaction for [demo.jpa.CompositeService.saveUserAndBook]
And then after exception is thrown:
Completing transaction for [demo.jpa.CompositeService.saveUserAndBook] after exception: java.lang.RuntimeException: User not saved
Applying rules to determine whether transaction should rollback on java.lang.RuntimeException: User not saved
Winning rollback rule is: null
No relevant rollback rule found: applying default rules
Clearing transaction synchronization
Here No relevant rollback rule found: applying default rules means that rules defined by DefaultTransactionAttribute will be applied to determine if transaction should be rolled back. And these rules are:
Rolls back on runtime, but not checked, exceptions by default.
RuntimeException is runtime exception, so the transaction will be rolled back.
The line Clearing transaction synchronization is where rollback is actually applied. You will see some other Applying rules to determine whether transaction should rollback messages because #Transactional methods are nested here (UserService.saveUser called from CompositeService.saveUserAndBook and both methods are #Transactional), but all they do is determine rules for future actions (at the point of transaction synchronization). The actual rollback will be done only once, at the outermost #Transactional method exit.
Since spring 3.1 if you're using spring-data-* or spring-tx dependencies on the classpath, then transaction management will be enabled by default.
https://www.baeldung.com/transaction-configuration-with-jpa-and-spring
But checking Springs Transactional annotation we can see that you'll need to inform the parameter rollbackFor if exception thown isn't a extension of RuntimeException.
/**
* Defines zero (0) or more exception {#link Class classes}, which must be
* subclasses of {#link Throwable}, indicating which exception types must cause
* a transaction rollback.
* <p>By default, a transaction will be rolling back on {#link RuntimeException}
* and {#link Error} but not on checked exceptions (business exceptions). See
* {#link org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)}
* for a detailed explanation.
* <p>This is the preferred way to construct a rollback rule (in contrast to
* {#link #rollbackForClassName}), matching the exception class and its subclasses.
* <p>Similar to {#link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class clazz)}.
* #see #rollbackForClassName
* #see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
*/
Class<? extends Throwable>[] rollbackFor() default {};
A simple #Transactional(rollbackFor = Exception.class) should work
The thing that you are trying to achieve here is not possible, as once you come out of the method after executing it; changes can not be reverted as you have #Transactional annotation.
Alternatively you could set auto commit false, and write a try catch block in methodA of class A. And if there is no exception commit the DB transaction, or else don't.

Spring Boot JSR-303/349 configuration

In my Spring Boot 1.5.1 application I'm trying to configure support of JSR-303 / JSR-349 validation.
I have added a following annotations #NotNull #Size(min = 1) to my method:
#Service
#Transactional
public class DecisionDaoImpl extends BaseDao implements DecisionDao {
#Override
public Decision create(#NotNull #Size(min = 1) String name, String description, String url, String imageUrl, Decision parentDecision, Tenant tenant, User user) {
...
}
}
I'm trying to invoke this method from my test, but it does not fail on the validation constraints.
This is my test and configs:
#SpringBootTest(classes = { TestConfig.class, Neo4jTestConfig.class })
#RunWith(SpringRunner.class)
#Transactional
public class TenantTest {
#Test
public void testCreateDecision() {
User user1 = userService.createUser("test1", "test1", "test1#test.com", null, null);
Tenant tenant1 = tenantDao.create("Tenant 1", "Tenant 1 description", false, user1);
// the following line should fail on the validation constraint because name parameter is null but it doesn't
final Decision rootDecision = decisionDao.create(null, "Root decision 1 description", null, tenant1, user1);
...
#Configuration
#ComponentScan("com.example")
#SpringBootApplication(exclude={Neo4jDataAutoConfiguration.class})
public class TestConfig {
}
What am I doing wrong and how to configure JSR-303 there ?
UPDATED
I have added
public Decision create(#Valid #NotNull #Size(min = 1) String name, String description, Decision parentDecision, Tenant tenant, User author) {
but it still doesn't work
I have added #Validated to my DecisionDaoImpl but it fails now with a following exception:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'decisionDaoImpl': Bean with name 'decisionDaoImpl' has been injected into other beans [criterionGroupDaoImpl,characteristicGroupDaoImpl,tenantDaoImpl] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:585)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:208)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1138)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
... 43 common frames omitted
I have also added #Lazy annotation in a places where I'm autowiring my DecisionDao but right now my test fails with a following exception:
javax.validation.ConstraintDeclarationException: HV000151: A method overriding another method must not alter the parameter constraint configuration, but method public com.example.domain.model.entity.decision.Decision com.example.domain.dao.decision.DecisionDaoImpl.create(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.Long,java.lang.Long,com.example.domain.model.entity.user.User) changes the configuration of public abstract com.example.domain.model.entity.decision.Decision com.example.domain.dao.decision.DecisionDao.create(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.Long,java.lang.Long,com.example.domain.model.entity.user.User).
at org.hibernate.validator.internal.metadata.aggregated.rule.OverridingMethodMustNotAlterParameterConstraints.apply(OverridingMethodMustNotAlterParameterConstraints.java:24)
at org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData$Builder.assertCorrectnessOfConfiguration(ExecutableMetaData.java:456)
Move your validation to interface, as follows:
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
public interface DecisionDao {
Decision create(#Valid #NotNull #Size(min = 1) String name,
String description, String url, String imageUrl);
}
Annotate your DecisionDaoImpl with #Validated, as follows:
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
#Service
#Validated
public class DecisionDaoImpl extends BaseDao implements DecisionDao {
#Override
public Decision create(String name,
String description, String url, String imageUrl) {
System.out.println(name);
return new Decision();
}
}
Modify your test case to verify for javax.validation.ConstraintViolationException using assertj or ExpectedException, as follows:
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import javax.validation.ConstraintViolationException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
#ContextConfiguration(classes = { TenantTest.Config.class })
#RunWith(SpringRunner.class)
public class TenantTest {
#Autowired
private DecisionDao decisionDao;
#Rule
public ExpectedException expectedException = ExpectedException.none();
#Test
public void testCreateDecisionUsingAssertj() {
assertThatExceptionOfType(ConstraintViolationException.class)
.isThrownBy(
() -> decisionDao.create(null,
"Root decision 1 description", null, null));
}
#Test
public void testCreateDecision() {
expectedException.expect(ConstraintViolationException.class);
decisionDao.create(null, "Root decision 1 description", null, null);
}
#Configuration
public static class Config {
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
#Bean
public DecisionDao decisionDao() {
return new DecisionDaoImpl();
}
}
}
Make sure you have hibernate-validator in your classpath along with #StanislavL answer:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
And an optional dependency for org.assertj.core.api.Assertions.assertThatExceptionOfType, as:
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.3.0</version>
<scope>test</scope>
</dependency>
For sample example, you can refer arpitaggarwal/jsr-303
You need #Valid annotation
Marks a property, method parameter or method return type for validation cascading.
Constraints defined on the object and its properties are be validated when the
property, method parameter or method return type is validated.
The constraint annotations are meant to applied to JavaBeans. See http://beanvalidation.org/1.0/spec/#constraintsdefinitionimplementation-constraintdefinition
You have the constraint annotation #NotNull, #Size, etc. applied within the DAO. You must create a Java Bean, e.g. "Person", that wraps those attributes (name, description, etc.), then pass "Person" as a parameter to the Controller method. If you need to use a DAO instead of a controller, it will need to be instrumented to perform the validation. You may be on your own in that regard with regard to AOP, etc., unless something has changed since this post: http://forum.spring.io/forum/spring-projects/container/82643-annotation-driven-jsr-303-validation-on-service-and-dao-tier
Update: Well, looks like it (method level validation JSR-349) is supported now see http://blog.codeleak.pl/2012/03/how-to-method-level-validation-in.html for an example, similar to Arpit's answer.
Updated title of question to reflect this latest JSR.

Use #Validated and #Valid with spring validator

I have a java bean being used to send JSON messages to a spring #RestController and I have bean validation setup and running just fine using #Valid. But I want to move to Protobuf/Thrift and move away from REST. It is an internal API and a lot of big companies have done away with REST internally. What this really means is that I no longer have control of the message objects - they are generated externally. I can't put annotations on them anymore.
So now my validation has to be programmatic. How do I do this? I have coded up a Validator and it works just great. But it doesn't use the nice #Valid annotation. I have to do the following:
#Service
public StuffEndpoint implements StuffThriftDef.Iface {
#Autowired
private MyValidator myValidator;
public void things(MyMessage msg) throws BindException {
BindingResult errors = new BeanPropertyBindingResult(msg, msg.getClass().getName());
errors = myValidator.validate(msg);
if (errors.hasErrors()) {
throw new BindException(errors);
} else {
doRealWork();
}
}
}
This stinks. I have to do this in every single method. Now, I can put a lot of that into one method that throws BindException and that makes it one line of code to add to every method. But that's still not great.
What I want is to see it look like this:
#Service
#Validated
public StuffEndpoint implements StuffThriftDef.Iface {
public void things(#Valid MyMessage msg) {
doRealWork();
}
}
And still get the same result. Remember, my bean has no annotations. And yes, I know I can use the #InitBinder annotation on a method. But that only works for web requests.
I don't mind injecting the correct Validator into this class, but I would prefer if my ValidatorFactory could pull the correct one based on the supports() method.
Is this possible? Is there a way to configure bean validation to actually use Spring validation instead? Do I have to hijack a Aspect somewhere? Hack into the LocalValidatorFactory or the MethodValidationPostProcessor?
Thanks.
Its pretty complicated thing to combine Spring validation and JSR-303 constrains. And there is no 'ready to use' way. The main inconvenience is that Spring validation uses BindingResult, and JSR-303 uses ConstraintValidatorContext as result of validation.
You can try to make your own validation engine, using Spring AOP. Let's consider, what we need to do for it. First of all, declare AOP dependencies (if you didn't yet):
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.8</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.8</version>
</dependency>
I'm using Spring of version 4.2.4.RELEASE, but of cause you can use your own. AspectJ needed for use aspect annotation. Next step, we have to create simple validator registry:
public class CustomValidatorRegistry {
private List<Validator> validatorList = new ArrayList<>();
public void addValidator(Validator validator){
validatorList.add(validator);
}
public List<Validator> getValidatorsForObject(Object o) {
List<Validator> result = new ArrayList<>();
for(Validator validator : validatorList){
if(validator.supports(o.getClass())){
result.add(validator);
}
}
return result;
}
}
As you see it is very simple class, which allow us to find validator for object. Now lets create annotation, that will be mark methods, that need to be validated:
package com.mydomain.validation;
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface CustomValidation {
}
Because of standard BindingException class is not RuntimeException, we can't use it in overriden methods. This means we need define our own exception:
public class CustomValidatorException extends RuntimeException {
private BindingResult bindingResult;
public CustomValidatorException(BindingResult bindingResult){
this.bindingResult = bindingResult;
}
public BindingResult getBindingResult() {
return bindingResult;
}
}
Now we are ready to create an aspect that will do most of the work. Aspect will execute before methods, which marked with CustomValidation annotation:
#Aspect
#Component
public class CustomValidatingAspect {
#Autowired
private CustomValidatorRegistry registry; //aspect will use our validator registry
#Before(value = "execution(public * *(..)) && annotation(com.mydomain.validation.CustomValidation)")
public void doBefore(JoinPoint point){
Annotation[][] paramAnnotations =
((MethodSignature)point.getSignature()).getMethod().getParameterAnnotations();
for(int i=0; i<paramAnnotations.length; i++){
for(Annotation annotation : paramAnnotations[i]){
//checking for standard org.springframework.validation.annotation.Validated
if(annotation.annotationType() == Validated.class){
Object arg = point.getArgs()[i];
if(arg==null) continue;
validate(arg);
}
}
}
}
private void validate(Object arg) {
List<Validator> validatorList = registry.getValidatorsForObject(arg);
for(Validator validator : validatorList){
BindingResult errors = new BeanPropertyBindingResult(arg, arg.getClass().getSimpleName());
validator.validate(arg, errors);
if(errors.hasErrors()){
throw new CustomValidatorException(errors);
}
}
}
}
execution(public * *(..)) && #annotation(com.springapp.mvc.validators.CustomValidation) means, that this aspect will applied to any public methods of beans, which marked with #CustomValidation annotation. Also note, that to mark validated parameters we are using standard org.springframework.validation.annotation.Validated annotation. But of cause we could make our custom. I think other code of aspect is very simple and does not need any comments. Further code of example validator:
public class PersonValidator implements Validator {
#Override
public boolean supports(Class<?> aClass) {
return aClass==Person.class;
}
#Override
public void validate(Object o, Errors errors) {
Person person = (Person)o;
if(person.getAge()<=0){
errors.rejectValue("age", "Age is too small");
}
}
}
Now we have make tune the configuration and all ready to use:
#Configuration
#ComponentScan(basePackages = "com.mydomain")
#EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig{
.....
#Bean
public CustomValidatorRegistry validatorRegistry(){
CustomValidatorRegistry registry = new CustomValidatorRegistry();
registry.addValidator(new PersonValidator());
return registry;
}
}
Note, proxyTargetClass is true because we will use cglib class proxy.
Example of target method in service class:
#Service
public class PersonService{
#CustomValidation
public void savePerson(#Validated Person person){
....
}
}
Because of #CustomValidation annotation aspect will be applied, and because of #Validated annotation person will be validated. And example of usage of service in controller(or any other class):
#Controller
public class PersonConroller{
#Autowired
private PersonService service;
public String savePerson(#ModelAttribute Person person, ModelMap model){
try{
service.savePerson(person);
}catch(CustomValidatorException e){
model.addAttribute("errors", e.getBindingResult());
return "viewname";
}
return "viewname";
}
}
Keep in mind, that if you will invoke #CustomValidation from methods of PersonService class, validation will not work. Because it will invoke methods of original class, but not proxy. This means, that you can invoke this methods only from outside of class (from other classes), if you want validation to be working (eg #Transactional works same way).
Sorry for long post. My answer is not about 'simple declarative way', and possible you will do not need it. But I was curious resolve this problem.
I marked #Ken's answer as correct because it is. But I have taken it a little further and wanted to post what I have made. I hope anybody coming to this page will find it interesting. I might try to get it in front of the Spring folks to see if it might be something included in future releases.
The idea is to have a new annotation to replace #Valid. So I called it #SpringValid. Using this annotation would kick off the system put together above. Here are all the pieces:
SpringValid.java
package org.springframework.validation.annotation;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
#Target({METHOD, FIELD, CONSTRUCTOR, PARAMETER})
#Retention(RUNTIME)
public #interface SpringValid {
}
SpringValidationAspect.java
package org.springframework.validation;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
#Aspect
#Component
public class SpringValidationAspect {
private SpringValidatorRegistry springValidatorRegistry;
#Autowired
public SpringValidationAspect(final SpringValidatorRegistry springValidatorRegistry) {
this.springValidatorRegistry = springValidatorRegistry;
}
public SpringValidatorRegistry getSpringValidatorRegistry() {
return springValidatorRegistry;
}
#Before("#target(org.springframework.validation.annotation.Validated) "
+ "&& execution(public * *(#org.springframework.validation.annotation.SpringValid (*), ..)) "
+ "&& args(validationTarget)")
public void beforeMethodThatNeedsValidation(Object validationTarget) {
validate(validationTarget);
}
private void validate(Object arg) {
List<Validator> validatorList = springValidatorRegistry.getValidatorsForObject(arg);
for (Validator validator : validatorList) {
BindingResult errors = new BeanPropertyBindingResult(arg, arg.getClass().getSimpleName());
validator.validate(arg, errors);
if (errors.hasErrors()) {
throw new SpringValidationException(errors);
}
}
}
}
Spring's examples show classes annotated with #Validated so I wanted to keep that. The above aspect only targets classes with #Validated at the class-level. And, just like when you use #Valid, it looks for the #SpringValid annotation stuck to a method parameter.
SpringValidationException.java
package org.springframework.validation;
import org.springframework.validation.BindingResult;
public class SpringValidationException extends RuntimeException {
private static final long serialVersionUID = 1L;
private BindingResult bindingResult;
public SpringValidationException(final BindingResult bindingResult) {
this.bindingResult = bindingResult;
}
public BindingResult getBindingResult() {
return bindingResult;
}
}
SpringValidatorRegistry.java
package org.springframework.validation;
import org.springframework.validation.Validator;
import java.util.ArrayList;
import java.util.List;
public class SpringValidatorRegistry {
private List<Validator> validatorList = new ArrayList<>();
public void addValidator(Validator validator) {
validatorList.add(validator);
}
public List<Validator> getValidatorsForObject(Object o) {
List<Validator> result = new ArrayList<>();
for (Validator validator : validatorList) {
if (validator.supports(o.getClass())) {
result.add(validator);
}
}
return result;
}
}
Just like the first answer, a place to register all classes that implement Spring's org.springframework.validation.Validator interface.
SpringValidator.java
package org.springframework.validation.annotation;
import org.springframework.stereotype.Component;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Component
public #interface SpringValidator {
}
This is just extra sauce to make it easier to register/find Validators. You could register all your Validators by hand, or you could find them via reflection. So this part is not required, I just thought it made things easier.
MyConfig.java
package com.example.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.SpringValidationAspect;
import org.springframework.validation.SpringValidatorRegistry;
import org.springframework.validation.annotation.SpringValidator;
import java.util.Map;
import javax.validation.Validator;
#Configuration
public class MyConfig {
#Autowired
private ApplicationContext applicationContext;
#Bean
public SpringValidatorRegistry validatorRegistry() {
SpringValidatorRegistry registry = new SpringValidatorRegistry();
Map<String, Object> validators =
applicationContext.getBeansWithAnnotation(SpringValidator.class);
validators.values()
.forEach(v -> registry.addValidator((org.springframework.validation.Validator) v));
return registry;
}
#Bean
public SpringValidationAspect springValidationAspect() {
return new SpringValidationAspect(validatorRegistry());
}
}
See, scan your classpath and look for #SpringValidator classes and register them. Then register the Aspect and away you go.
Here is an example of such a Validator:
MyMessageValidator.java
package com.example.validators;
import com.example.messages.MyMessage;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.SpringValidator;
#SpringValidator
public class MyMessageValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return MyMessage.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmpty(errors, "firstField", "{javax.validation.constraints.NotNull}",
"firstField cannot be null");
MyMessage obj = (MyMessage) target;
if (obj.getSecondField != null && obj.getSecondField > 100) {
errors.rejectField(errors, "secondField", "{javax.validation.constraints.Max}", "secondField is too big");
}
}
}
And here is the service class that uses the #SpringValid annotation:
MyService.java
package com.example.services;
import com.example.messages.MyMessage;
import org.springframework.validation.annotation.SpringValid;
import org.springframework.validation.annotation.Validated;
import javax.inject.Inject;
#Validated
public class MyService {
public String doIt(#SpringValid final MyMessage msg) {
return "we did it!";
}
}
Hope this makes sense for someone at some point. I personally think it is quite useful. A lot of companies are starting to move their internal APIs away from REST and to something like Protobuf or Thrift. You can still use Bean Validation but you have to use XML, and it isn't all that nice. So I hope this will be helpful to people who want to still do programmatic validation.
Hope it helps someone. I've got it working by adding the following configuration:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
#Configuration
public class ValidatorConfiguration {
#Bean
public MethodValidationPostProcessor getMethodValidationPostProcessor(){
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
processor.setValidator(this.validator());
return processor;
}
#Bean
public LocalValidatorFactoryBean validator(){
return new LocalValidatorFactoryBean();
}
}
The service is then annotated the same way (#Validated on the class and #Valid on the parameter) and can be injected into another bean where the method can be called directly and validation happens.

Mocking CGLIB enhanced objects

Is it true that mockito can't mock objects that were already enhanced by CGLIB?
public class Article {
#Autowired
private dbRequestHandler
#Autowired
private filesystemRequestHandler
#Transactional
public ArticleDTO getArticleContents() {
//extractText() and then save the data in DTO
//extractImages() and then save the data in DTO
// some other calls to other databases to save data in dto
return articleDTO;
}
public void extractText() {
//call to DB
}
public void extractImages() {
// call to file system
}
}
public class IntegrationTest {
#Autowired
private Article article;
//setup method {
articleMock = Mockito.spy(article);
doNothing().when(articleMock).extractImages();
}
}
In the above example when it comes to doNothing().when(articleMock).extractImages(); it actually calls the real function. On a closer look articleMock gets enhanced two times. One cause of autowiring and second time cause of spying.
If I can't spy on enhaced objects, then how can I test the getArticle() method in my Integration test, so that I can verify a proper DTO is returned.
Note : I actually don't want to test the method which does filesystem calls. just the DB ones. thats why I need to test the getArticle method.
If I understand correctly your class is wired by Spring. Spring uses CGLIB to ensure transactional behaviour only if there is no interface, which is implemented by your object. If there is an interface, it uses simple JDK Dynamic Proxies. (see http://docs.spring.io/spring/docs/3.0.0.M3/reference/html/ch08s06.html)
Maybe you could try to extract an interface, and let Spring to use dynamic proxies. Maybe then Mockito could perform better.
If you run as a true unit test and not as an integration test, you need not run in a container having Spring autowire for you. In one of your comments, I think you alluded to trying this, and you noted that there was an endless set of chained object references which you would have to provide as well. But there is a way around that. Mockito provides some predefined Answer classes that you can initialize your mock with. You may want to look at RETURNS_DEEP_STUBS, which will possibly get you around this problem.
Will you please update your question with ready-to-go compilable code. Here's some code review suggestions:
Issues with this question code:
Article.java missing import: org.springframework.beans.factory.annotation.Autowired
Article.java missing import: org.springframework.transaction.annotation.Transactional
Article.java attribute syntax issue: dbRequestHandler
Article.java attribute syntax issue: filesystemRequestHandler
Article.java method has no initialized return statement: articleDTO
Here's what you maybe should use as you questionCode with the above issues fixed:
Article.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
public class Article {
#Autowired
private Object dbRequestHandler;
#Autowired
private Object filesystemRequestHandler;
#Transactional
public ArticleDTO getArticleContents() {
// extractText() and then save the data in DTO
// extractImages() and then save the data in DTO
// some other calls to other databases to save data in dto
ArticleDTO articleDTO = null;
return articleDTO;
}
public void extractText() {
// call to DB
}
public void extractImages() {
// call to file system
}
}
IntegrationTest.java is a poor name for a testClass because it's to generic. I would suggest ArticleTest for a java unit test.
ArticleTest.java
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.beans.factory.annotation.Autowired;
#RunWith(PowerMockRunner.class)
#PrepareForTest(ClassWithPrivate.class)
public class ArticleTest {
#InjectMocks
private Article cut;
#Mock
private Object dbRequestHandler;
#Mock
private Object filesystemRequestHandler;
#Test
public void testeExtractImages() {
/* Initialization */
Article articleMock = Mockito.spy(cut);
/* Mock Setup */
Mockito.doNothing().when(articleMock).extractImages();
/* Test Method */
ArticleDTO result = cut.getArticleContents();
/* Asserts */
Assert.assertNull(result);
}
}
You can utilize AdditionalAnswers.delegatesTo method. In following example, the secondProxyDoingMocking declaration creates something like a spy (compare with implementation of spy() method) except it uses "lightweight" method delegation.
import org.mockito.AdditionalAnswers;
public class ArticleTest {
#Autowired
private Article firstProxyDoingAutowiring;
#Test
public void testExtractImages() {
Article secondProxyDoingMocking = Mockito.mock(Article.class,
Mockito.withSettings().defaultAnswer(
AdditionalAnswers.delegatesTo(firstProxyDoingAutowiring)
)
);
Mockito.doNothing().when(secondProxyDoingMocking).extractImages();
...
}
}
I didn't test this example, however I assembled it from my working code. My use case was similar: return constant value for given method, call real method for all remaining methods of Spring #Transactional-annotated bean.

Categories