Tôi đang sử dụng Spring Validator triển khai để xác nhận đối tượng của tôi và tôi muốn biết làm thế nào để bạn viết một bài kiểm tra đơn vị cho một validator như thế này:Viết thử nghiệm JUnit để thực hiện mùa xuân 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();
}
}
}
Làm thế nào tôi có thể đơn vị kiểm tra CustomerValidator mà không cần gọi thực hiện thực tế của AddressValidator (bằng cách chế nhạo nó)? Tôi đã không thấy bất kỳ ví dụ nào như vậy ...
Nói cách khác, những gì tôi thực sự muốn làm ở đây là giả lập AddressValidator được gọi và instanciated bên trong CustomerValidator ... có cách nào để giả lập AddressValidator này?
Hoặc có thể tôi đang xem sai đường? Có lẽ những gì tôi cần làm là để giả mạo các cuộc gọi đến ValidationUtils.invokeValidator (...), nhưng sau đó một lần nữa, tôi không chắc chắn làm thế nào để làm một điều như vậy.
Mục đích của những gì tôi muốn làm thực sự đơn giản. AddressValidator đã được kiểm tra đầy đủ trong một lớp thử nghiệm khác (hãy gọi nó là AddressValidatorTestCase). Vì vậy, khi tôi đang viết lớp JUnit của tôi cho CustomerValidator, tôi không muốn "kiểm tra lại" nó trên một lần nữa ... vì vậy tôi muốn AddressValidator luôn luôn trở lại mà không có lỗi (thông qua ValidationUtils.invokeValidator (. ..) gọi điện).
Cảm ơn sự giúp đỡ của bạn.
EDIT (2012/03/18) - Tôi đã tìm được giải pháp tốt (tôi nghĩ ...) bằng cách sử dụng JUnit và Mockito làm khung mocking.
Thứ nhất, lớp thử nghiệm 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());
}
// ...
}
Các AddressValidator được kiểm tra đầy đủ với lớp này. Đây là lý do tại sao tôi không muốn "kiểm tra lại" nó một lần nữa trong CustomerValidator. Bây giờ, lớp kiểm tra 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());
}
// ...
}
Đó là về nó. Tôi thấy giải pháp này khá sạch sẽ ... nhưng hãy cho tôi biết suy nghĩ của bạn. Liệu nó có tốt không? Nó có quá phức tạp không? Cảm ơn phản hồi của bạn.
Bạn nên tạo câu trả lời thay vì sau đó chỉnh sửa câu hỏi gốc bằng câu trả lời. Sau đó, bạn có thể chấp nhận câu trả lời của bạn (nếu bạn nghĩ rằng nó vẫn là tốt nhất). Đó là cách bình thường để xử lý kịch bản này mà tôi tin. Nó luôn luôn tốt để có một câu trả lời được chấp nhận nếu những người khác có cùng một vấn đề. – Dave
Những dòng này nên tham khảo customerValidator tôi nghĩ, không phải là addressValidator. Tôi không thực sự thấy điểm trong việc xác minh rằng validator.supports được gọi - đó là lời gọi phương thức hợp lệ mà bạn quan tâm. Tôi sẽ nói. xác minh (addressValidator) .supports (Address.class); // verify (addressValidator) .validate (customer.getAddress(), errors); –
Điều này chỉ kiểm tra đường dẫn "hạnh phúc". Làm thế nào bạn có thể kiểm tra rằng một thất bại AddressValidator làm cho CustomerValidator thất bại với lỗi địa chỉ mặc dù tất cả các chi tiết khách hàng khác có thể là chính xác? –