gpt4 book ai didi

spring - 为 Spring Validator 实现编写 JUnit 测试

转载 作者:IT老高 更新时间:2023-10-28 13:49:07 25 4
gpt4 key购买 nike

我正在使用 Spring 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();
}
}
}

如何在不调用 AddressValidator 的实际实现(通过模拟它)的情况下对 CustomerValidator 进行单元测试?没见过这样的例子……

换句话说,我在这里真正想做的是模拟在 CustomerValidator 内部调用和实例化的 AddressValidator...有没有办法模拟这个 AddressValidator?

或者我看错了?也许我需要做的是模拟对 ValidationUtils.invokeValidator(...) 的调用,但话又说回来,我不知道该怎么做。

我想做的事情的目的很简单。 AddressValidator 已经在另一个测试类中进行了全面测试(我们称之为 AddressValidatorTestCase)。因此,当我为 CustomerValidator 编写 JUnit 类时,我不想重新“重新测试”它......所以我希望 AddressValidator 始终返回没有错误(通过 ValidationUtils.invokeValidator(. ..) 打电话)。

感谢您的帮助。

编辑 (2012/03/18) -我已经设法找到了一个很好的解决方案(我认为...),使用 JUnit 和 Mockito 作为模拟框架。

一、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());
}

// ...
}

AddressValidator 已通过此类进行了全面测试。这就是为什么我不想在 CustomerValidator 中重新“重新测试”它。现在,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());
}

// ...
}

就是这样。我发现这个解决方案很干净......但让我知道你的想法。好吗?是不是太复杂了?感谢您的反馈。

最佳答案

这是一个非常直接的测试,没有任何模拟。 (只是错误对象的创建有点棘手)

@Test
public void testValidationWithValidAddress() {
AdressValidator addressValidator = new AddressValidator();
CustomValidator validatorUnderTest = new CustomValidator(adressValidator);

Address validAddress = new Address();
validAddress.set... everything to make it valid

Errors errors = new BeanPropertyBindingResult(validAddress, "validAddress");
validatorUnderTest.validate(validAddress, errors);

assertFalse(errors.hasErrors());
}


@Test
public void testValidationWithEmptyFirstNameAddress() {
AdressValidator addressValidator = new AddressValidator();
CustomValidator validatorUnderTest = new CustomValidator(adressValidator);

Address validAddress = new Address();
invalidAddress.setFirstName("")
invalidAddress.set... everything to make it valid exept the first name

Errors errors = new BeanPropertyBindingResult(invalidAddress, "invalidAddress");
validatorUnderTest.validate(invalidAddress, errors);

assertTrue(errors.hasErrors());
assertNotNull(errors.getFieldError("firstName"));
}

顺便说一句:如果你真的想让它更复杂并通过模拟使其复杂化,那么看看 this Blog ,他们使用两个模拟,一个用于测试对象(好的,如果你不能创建一个,这很有用),第二个用于 Error 对象(我认为这更复杂一定是。)

关于spring - 为 Spring Validator 实现编写 JUnit 测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9744988/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com