sto usando Spring Validator implementazioni per convalidare il mio oggetto e vorrei sapere come si fa a scrivere un unit test per un validatore come questo:scrittura test JUnit per l'attuazione Primavera Validator
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();
}
}
}
Come posso unit test CustomerValidator senza chiamare la vera implementazione di AddressValidator (prendendolo in giro)? Non ho visto nessun esempio del genere ...
In altre parole, quello che voglio veramente fare qui è prendere in giro AddressValidator che viene chiamato e instanciated all'interno del CustomerValidator ... c'è un modo per prendi in giro questo AddressValidator?
O forse lo sto guardando nel modo sbagliato? Forse quello che devo fare è prendere in giro la chiamata a ValidationUtils.invokeValidator (...), ma poi di nuovo, non sono sicuro di come fare una cosa del genere.
Lo scopo di ciò che voglio fare è davvero semplice. AddressValidator è già completamente testato in un'altra classe di test (chiamiamola th AddressValidatorTestCase). Quindi, quando scrivo la mia classe JUnit per il CustomerValidator, non voglio "re-testare" tutto da capo ... quindi voglio che AddressValidator torni sempre senza errori (attraverso ValidationUtils.invokeValidator (. ..) chiamata).
Grazie per il vostro aiuto.
EDIT (2012/03/18) - sono riuscito a trovare una buona soluzione (credo ...) utilizzando JUnit e Mockito come il quadro di scherno.
In primo luogo, la classe di test 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());
}
// ...
}
L'AddressValidator è completamente testato con questa classe. Questo è il motivo per cui non voglio "ri-testare" tutto da capo in CustomerValidator. Ora, la classe di test 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());
}
// ...
}
Questo è tutto. Ho trovato questa soluzione abbastanza pulita ... ma fammi sapere cosa ne pensi. È buono? È troppo complicato? Grazie per il vostro feedback.
Dovresti aver creato una risposta piuttosto che modificare la domanda originale con una risposta. Quindi potresti accettare la tua risposta (se pensavi che fosse ancora la migliore). Questo è il modo normale di gestire questo scenario, credo. È sempre bene avere una risposta accettata se altre persone hanno lo stesso problema. – Dave
Queste righe dovrebbero fare riferimento a customerValidator, penso, non all'indirizzoValidator. Non vedo davvero il punto nel verificare che validator.supports sia chiamato - è l'invocazione del metodo di validazione a cui sei interessato. Direi. verificare (addressValidator) .Supporto (Address.class); // verify (addressValidator) .validate (customer.getAddress(), errors); –
Questo test solo il percorso "felice". Come si può verificare che un errore di AddressValidator abbia esito negativo nel CustomerValidator con errori di indirizzo anche se tutti gli altri dettagli del cliente possono essere corretti? –