Scriptico

Add to Google Reader or Homepage

How to Test JSR 303 Bean Validators

If you ever see how the JSR 303 validation works, you probably know how awesome this way is. I like it because it makes the code more readable, easy to maintain and to add new features. Additional information about the approach is available here, and in this article, I am going to show how to test JSR 303.
First of all, we need a validator to test, right? Let’s consider the following data transfer object or if you wish, we can call it a bean:

package com.foo.logic.dto;

import java.io.Serializable;
import com.foo.web.validation.EmailExistsConstraint;

public final class UserCredentialsDto implements
		Serializable {

	private static final long serialVersionUID = 1L;

	private String userId;

	@EmailExistsConstraint(message = "Provided email already exists")
	private String email;
	
	private String password;

	public UserCredentialsDto() {
		super();
	}

        //setters, getters
}

Now, let’s assume that we want to validate the email property. In order to do so, we have to just put an annotation before the property definition and that is it. So, the email validator looks like it is shown below:

// EmailExistsConstraint.java
package com.foo.web.validation;

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;

import javax.validation.Constraint;
import javax.validation.Payload;


@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = EmailExistsConstraintValidator.class)
public @interface EmailExistsConstraint {
	String message();

	Class[] groups() default {};

	Class[] payload() default {};
}


//EmailExistsConstraintValidator.java
package com.foo.web.validation;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class EmailExistsConstraintValidator implements
		ConstraintValidator {

	@Override
	public void initialize(EmailExistsConstraint arg0) {
	}

	@Override
	public boolean isValid(Object candidate, ConstraintValidatorContext arg1) {
		return ((String) candidate).equals("ok@email.com");
	}
}

Well, we are done with the preparation. Yes, I know. Tests at the begging and then the implementation, but to keep the article clear, I did it in the wrong order, so just forgive me lol.
Ok, now is the test.

package junit.com.foo.web.validation;

import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

import junit.framework.Assert;

import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import com.foo.logic.dto.UserCredentialsDto;

public class EmailExistsConstraintValidatorTest {

	private static final String CORRECT_EMAIL = "ok@email.com";
	private static final String INCORRECT_EMAIL = "notok@email.com";

	private static Validator validator;

	private UserCredentialsDto credentials;

	@BeforeClass
	public static void setup() {
		ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
		validator = factory.getValidator();
	}

	@Before
	public void before() {
		credentials = new UserCredentialsDto();
	}

	@Test
	public void testEmailExistsCorrect() {
		credentials.setEmail(CORRECT_EMAIL);

		Set> violations = validator
				.validate(credentials);
		Assert.assertEquals(0, violations.size());
	}

	@Test
	public void testEmailExistsIncorrect() {
		credentials.setEmail(INCORRECT_EMAIL);

		Set> violations = validator
				.validate(credentials, UserCredentialsDto.class);
		Assert.assertEquals(1, violations.size());
	}
}

As you can see, it is really easy to write a test case to cover a validator. However, in some particular cases some issues may come up. Let’s just briefly look at some possible problems.

1. The validator has the autowired service (Spring Framework).

The test case above will not work and it has to be changed a little bit. Let’s assume we do have the following UserService class:

public class UserService {

	@Autowired
	private UserDao userDao;

	public final boolean isEmailExist(String email) throws ServiceException {
		return userDao.isEmailExist(email);
	}
}

and its instance is autowired into the EmailExistsConstraintValidator validator as shown below:

public class EmailExistsConstraintValidator implements
		ConstraintValidator {

	@Autowired
	private UserService userService;

	@Override
	public void initialize(EmailExistsConstraint arg0) {
	}

	@Override
	public boolean isValid(Object candidate, ConstraintValidatorContext arg1) {
		return !userService.isEmailExist((String) candidate);
	}
}

So, if you run the test class with no changes you will get the NullPointer exception, because the instance of the UserService class in the validator will not be autowired. In order to avoid it, we have to use spring instruments in our test case as it is shown below:

package junit.com.foo.web.validation;

import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validator;

import junit.framework.Assert;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "test-context.xml" })
@TransactionConfiguration(defaultRollback = false)
@Transactional
public class EmailExistsConstraintValidatorTest {

	private static final String NEW_EMAIL = "correct@email.com";

	@Autowired
	private Validator validator;

	private UserCredentialsDto credentials;

	@Before
	public void before() {

		credentials = new UserCredentialsDto();
	}

// test methods

Since we autowire the validator into the test, we have to define the LocalValidatorFactoryBean bean in the test context configuration :




	
	
	

   

That is it!

2. You receive the java.lang.ClassFormatError exception in every attempt to run the test.
A pretty weird exception. Check your class path and remove javaee-api.jar from it. The jar file only has interfaces with no implementation and as a result, you receive the exception with the following stack trace:

java.lang.ClassFormatError: Absent Code attribute in method that is not native or abstract in class file javax/validation/Validation
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
....

Just try to remove the jar file and run your test one more time. In my case it has helped to solve the problem.

Probably, that is it. Now we can cover all our code by tests! Feel free to ping me back if you have any questions.

Category: Development, Java, Spring, WebApp

Tagged:

Comments are closed.