为 Spring Validator 实现编写 JUnit 测试 [英] Writing JUnit tests for Spring Validator implementation

查看:28
本文介绍了为 Spring Validator 实现编写 JUnit 测试的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 Spring Validator 用于验证我的对象的实现,我想知道您如何为这样的验证器编写单元测试:

I'm using Spring Validator implementations to validate my object and I would like to know how do you write a unit test for a validator like this one:

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 进行单元测试?我还没有见过这样的例子......

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...

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

In other words, 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?

也许我看错了?也许我需要做的是模拟对 ValidationUtils.invokeValidator(...) 的调用,但话说回来,我不知道如何做这样的事情.

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.

我想做的事情的目的很简单.AddressValidator 已经在另一个测试类中进行了全面测试(我们称之为 AddressValidatorTestCase).因此,当我为 CustomerValidator 编写 JUnit 类时,我不想再次重新测试"它……所以我希望 AddressValidator 始终无错误地返回(通过 ValidationUtils.invokeValidator(...) 调用).

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).

感谢您的帮助.

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

EDIT (2012/03/18) - I've managed to find a good solution (I think...) using JUnit and Mockito as the mocking framework.

首先,AddressValidator 测试类:

First, the AddressValidator test class:

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 测试类:

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:

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 for your feedback.

推荐答案

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

It is a really straight forward test without any mock. (just the error-object creation is a bit tricky)

@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"));
}

顺便说一句:如果你真的想让它变得更复杂并通过模拟让它变得复杂,那么看看 这个博客,他们使用了两个模拟,一个用于测试对象(好吧,这很有用,如果你不能创建一个),第二个用于 Error 对象(我认为这必须更复杂.)

BTW: if you really want to make it more complicate and make it complicate by a mock, then have a look at this Blog, they use a two mocks, one for the object to test (ok, this is useful if you can not create one), and a second for the Error object (I think this is more complicated the it must be.)

这篇关于为 Spring Validator 实现编写 JUnit 测试的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆