Announcement Announcement Module
Collapse
No announcement yet.
Writing JUnit tests for Spring Validator implementation Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • 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
    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);
        }
    
    }

    Comment


    • #3
      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, 11:52 PM.

      Comment


      • #4
        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());
            	// ...
            }
        
        }

        Comment


        • #5
          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.

          Comment


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

            Comment

            Working...
            X