I'm using Spring Validator implementations to validate my object and I would like to know how do you write a unit test for a validator like this one:
public class CustomerValidator implements Validator {
private final Validator addressValidator;
public CustomerValidator(Validator addressValidator) {
if (addressValidator == null) {
throw new IllegalArgumentException(
"The supplied [Validator] is required and must not be null.");
}
if (!addressValidator.supports(Address.class)) {
throw new IllegalArgumentException(
"The supplied [Validator] must support the validation of [Address] instances.");
}
this.addressValidator = addressValidator;
}
/**
* This Validator validates Customer instances, and any subclasses of Customer too
*/
public boolean supports(Class clazz) {
return Customer.class.isAssignableFrom(clazz);
}
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required");
Customer customer = (Customer) target;
try {
errors.pushNestedPath("address");
ValidationUtils.invokeValidator(this.addressValidator, customer.getAddress(), errors);
} finally {
errors.popNestedPath();
}
}
}
How can I unit test CustomerValidator without calling the real implementation of the AddressValidator (by mocking it)? I've haven't seen any example like that...
In other words, what I really want to do here is to mock the AddressValidator which is called and instanciated inside the CustomerValidator... is there a way to mock this AddressValidator?
Or maybe I'm looking at it the wrong way? Maybe what I need to do is to mock the call to ValidationUtils.invokeValidator(...), but then again, I'm not sure how to do such a thing.
The purpose of what I want to do is really simple. The AddressValidator is already fully tested in another test class (let's call it th AddressValidatorTestCase). So when I'm writing my JUnit class for the CustomerValidator, I don't want to "re-test" it all over again... so I want the AddressValidator to always return with no errors (through the ValidationUtils.invokeValidator(...) call).
Thanks for your help.
EDIT (2012/03/18) - I've managed to find a good solution (I think...) using JUnit and Mockito as the mocking framework.
First, the AddressValidator test class:
public class Address {
private String city;
// ...
}
public class AddressValidator implements org.springframework.validation.Validator {
public boolean supports(Class<?> clazz) {
return Address.class.equals(clazz);
}
public void validate(Object obj, Errors errors) {
Address a = (Address) obj;
if (a == null) {
// A null object is equivalent to not specifying any of the mandatory fields
errors.rejectValue("city", "msg.address.city.mandatory");
} else {
String city = a.getCity();
if (StringUtils.isBlank(city)) {
errors.rejectValue("city", "msg.address.city.mandatory");
} else if (city.length() > 80) {
errors.rejectValue("city", "msg.address.city.exceeds.max.length");
}
}
}
}
public class AddressValidatorTest {
private Validator addressValidator;
@Before public void setUp() {
validator = new AddressValidator();
}
@Test public void supports() {
assertTrue(validator.supports(Address.class));
assertFalse(validator.supports(Object.class));
}
@Test public void addressIsValid() {
Address address = new Address();
address.setCity("Whatever");
BindException errors = new BindException(address, "address");
ValidationUtils.invokeValidator(validator, address, errors);
assertFalse(errors.hasErrors());
}
@Test public void cityIsNull() {
Address address = new Address();
address.setCity(null); // Already null, but only to be explicit here...
BindException errors = new BindException(address, "address");
ValidationUtils.invokeValidator(validator, address, errors);
assertTrue(errors.hasErrors());
assertEquals(1, errors.getFieldErrorCount("city"));
assertEquals("msg.address.city.mandatory", errors.getFieldError("city").getCode());
}
// ...
}
The AddressValidator is fully tested with this class. This is why I don't want to "re-test" it all over again in the CustomerValidator. Now, the CustomerValidator test class:
public class Customer {
private String firstName;
private Address address;
// ...
}
public class CustomerValidator implements org.springframework.validation.Validator {
// See the first post above
}
@RunWith(MockitoJUnitRunner.class)
public class CustomerValidatorTest {
@Mock private Validator addressValidator;
private Validator customerValidator; // Validator under test
@Before public void setUp() {
when(addressValidator.supports(Address.class)).thenReturn(true);
customerValidator = new CustomerValidator(addressValidator);
verify(addressValidator).supports(Address.class);
// DISCLAIMER - Here, I'm resetting my mock only because I want my tests to be completely independents from the
// setUp method
reset(addressValidator);
}
@Test(expected = IllegalArgumentException.class)
public void constructorAddressValidatorNotSupplied() {
customerValidator = new CustomerValidator(null);
fail();
}
// ...
@Test public void customerIsValid() {
Customer customer = new Customer();
customer.setFirstName("John");
customer.setAddress(new Address()); // Don't need to set any fields since it won't be tested
BindException errors = new BindException(customer, "customer");
when(addressValidator.supports(Address.class)).thenReturn(true);
// No need to mock the addressValidator.validate method since according to the Mockito documentation, void
// methods on mocks do nothing by default!
// doNothing().when(addressValidator).validate(customer.getAddress(), errors);
ValidationUtils.invokeValidator(customerValidator, customer, errors);
verify(addressValidator).supports(Address.class);
// verify(addressValidator).validate(customer.getAddress(), errors);
assertFalse(errors.hasErrors());
}
// ...
}
That's about it. I found this solution pretty clean... but let me know what you think. Is it good? Is it too complicated? Thanks for your feedback.