estoy usando Spring Validator implementaciones para validar mi objetivo y me gustaría saber cómo se escribe una prueba unitaria para un validador como éste:escribir pruebas JUnit para la implementación de Primavera Validador
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();
}
}
}
¿Cómo puedo prueba la unidad CustomerValidator sin llamar a la implementación real de AddressValidator (burlándose de ella)? No he visto ningún ejemplo como ese ...
En otras palabras, lo que realmente quiero hacer aquí es simular el AddressValidator que se llama y se instancia en el CustomerValidator ... ¿hay alguna manera de ¿se burla de este AddressValidator?
¿O quizás lo estoy viendo de la manera incorrecta? Tal vez lo que tengo que hacer es burlarme de la llamada a ValidationUtils.invokeValidator (...), pero, nuevamente, no estoy seguro de cómo hacer tal cosa.
El propósito de lo que quiero hacer es realmente simple. El AddressValidator ya está completamente probado en otra clase de prueba (llamémoslo th AddressValidatorTestCase). Así que cuando estoy escribiendo mi clase JUnit para el CustomerValidator, no quiero volver a probarlo todo ... por lo que quiero que el AddressValidator siempre regrese sin errores (a través de ValidationUtils.invokeValidator (. ..) llamada).
Gracias por su ayuda.
EDITAR (2012/03/18) - He logrado encontrar una buena solución (creo ...) utilizando JUnit y Mockito como marco de burla.
En primer lugar, la clase de prueba AddressValidator:
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());
}
// ...
}
El AddressValidator se prueba completamente con esta clase. Es por eso que no quiero volver a probarlo todo en el CustomerValidator. Ahora, la clase de prueba CustomerValidator:
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());
}
// ...
}
Eso es todo. Encontré esta solución bastante limpia ... pero déjame saber lo que piensas. ¿Esta bien? ¿Es demasiado complicado? Gracias por su respuesta.
Debería haber creado una respuesta en lugar de editar la pregunta original con una respuesta. Entonces podrías aceptar tu respuesta (si crees que todavía es la mejor). Esa es la forma normal de manejar este escenario, creo. Siempre es bueno tener una respuesta aceptada si otras personas tienen el mismo problema. – Dave
Estas líneas deberían referirse al customerValidator creo, no al addressValidator. Realmente no veo el punto de verificar que se llame a validator.supports, es la invocación al método de validación que le interesa. Yo diría. verify (addressValidator) .supports (Address.class); // verify (addressValidator) .validate (customer.getAddress(), errors); –
Esto solo prueba la ruta "feliz". ¿Cómo se puede probar que una falla de AddressValidator hace que CustomerValidator falle con errores de dirección a pesar de que todos los demás datos del cliente pueden ser correctos? –