Results 1 to 6 of 6

Thread: Writing JUnit tests for Spring Validator implementation

  1. #1

    Question Writing JUnit tests for Spring Validator implementation

    Hi,
    I'm using Spring Validator implementations (http://static.springsource.org/sprin...alidation.html) to validate my object and I would like to know how do you write a unit test for a validator like this one:

    Code:
    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...

    Thanks.

  2. #2
    Join Date
    May 2011
    Location
    Madrid (Spain)
    Posts
    101

    Default

    Hi, you can create an interface:

    Code:
    public interface CustomerValidator extends Validator {
    }
    and your implementation:

    Code:
    public class CustomerValidatorImpl implements CustomerValidator {
    ...
    }
    so, you can test with a mock:

    Code:
    public class ValidatorMockTests {
    
        private CustomerValidator validator;
    
        @Before
        public void init() {
        	validator = EasyMock.createMock(CustomerValidator.class);
        }
    
        @Test
        public void supports() {
        	validator.supports(null);
            EasyMock.expectLastCall().andReturn(true);
            EasyMock.replay(validator);
            boolean supports = validator.supports(null);
            EasyMock.verify(validator);
            assertEquals(true, supports);
        }
    
    }

  3. #3

    Default

    Thanks for the reply.
    However, 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).
    Last edited by rubberballman; Mar 16th, 2012 at 11:52 PM.

  4. #4
    Join Date
    May 2011
    Location
    Madrid (Spain)
    Posts
    101

    Default

    OK, no problem:

    Code:
    public class ValidatorMockTests {
    
        private Validator addressValidator;
        private CustomerValidator customerValidator;
    
        @Before
        public void init() {
        	addressValidator = EasyMock.createMock(Validator.class);
        	addressValidator.supports(Address.class);
        	EasyMock.expectLastCall().andReturn(true);
        	EasyMock.replay(addressValidator);
        	customerValidator = new CustomerValidator(addressValidator);
        	EasyMock.verify(addressValidator);
        }
    
        @Test
        public void myTest() {
        	customerValidator.supports(this.getClass());
        	// ...
        }
    
    }

  5. #5

    Default

    Thanks for your reply.
    I've managed to find a good solution (I think...) using JUnit and Mockito as the mocking framework instead of EasyMock.

    First, the AddressValidator test class:

    Code:
    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:

    Code:
    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.

  6. #6
    Join Date
    May 2011
    Location
    Madrid (Spain)
    Posts
    101

    Default

    I think it's good, now you can use JUnit to test them in combination (integration tests).

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •