Martin Gladdish

Software 'n' stuff

Testing Jsr303 Annotated Beans

I have been looking at using jsr303 annotations to mark up some of my beans. The good news is that both Hibernate and Spring play nicely with these annotations, and will validate them at appropriate times. The bad news is that the examples of testing these annotations that I have found online so far look cumbersome and awkward.

Let’s take a simple example:

simple java bean with a single jsr303 annotation
1
2
3
4
5
6
7
8
9
10
public class SimpleBean {
    @NotNull
    private Long id;
    private String name;

    public SimpleBean(Long id, String name) {
        this.id = id; this.name = name;
    }
    // getters and setters...
}

This is fine so far and we have the possibility that the constructor could be passed a null id. So, let’s write a unit test that ensures our validation will barf.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SimpleBeanTest {

    private Validator validator;

    @Before
    public void setUp() {
        validator = Validation.buildDefaultValidatorFactory().getValidator();
    }

    @Test
    public void nullIdMustThrowValidationError() {
        SimpleBean bean = new SimpleBean(null, "valid string");
        Set<ConstraintViolation<SimpleBean>> violations = validator.validate(bean);
        assertThat(violations.size(), equalTo(1));
    }
}

If this test fails, we get something like this as the result:

1
2
3
4
java.lang.AssertionError: 
Expected: <1>
     but: was <0>
  ...

Not so pretty. This is where hamcrest’s matcher toolkit comes in handy. What if we could write our test like this instead?

unit test with custom validations matcher
1
2
3
4
5
6
7
public class SimpleBeanTest {
    @Test
    public void nullIdMustThrowValdationError() {
        SimpleBean bean = new SimpleBean(null, "valid string");
        assertThat(bean, hasNoViolations());
    }
}

A matcher implementation could look like this

custom jsr303 validation matcher
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class JSR303NoViolationsMatcher<T> extends TypeSafeMatcher<T> {

    private Validator validator;
    private Set<ConstraintViolation<T>> violations;

    public JSR303NoViolationsMatcher() {
        validator = Validation.buildDefaultValidatorFactory().getValidator();
    }

    @Override
    protected boolean matchesSafely(T t) {
        violations = validator.validate(t);
        return violations.size() == 0;
    }

    @Override
    public void describeTo(Description description) {
        description.appendText("no jsr303 validation violations");
    }

    @Override
    protected void describeMismatchSafely(Object item, Description mismatchDescription) {
        mismatchDescription.appendText("was ").appendValue(violations);
    }

    @Factory
    public static JSR303NoViolationsMatcher hasNoViolations() {
        return new JSR303NoViolationsMatcher();
    }
}

And our new-look test spits out the following upon failure:

1
2
3
java.lang.AssertionError: 
Expected: no jsr303 validation violations
     but: was <[ConstraintViolationImpl{interpolatedMessage='may not be null', propertyPath=id, rootBeanClass=class com.example.SimpleBean, messageTemplate='{javax.validation.constraints.NotNull.message}'}]>

There. Much nicer. Not only do we know that the test failed, but we are immediately told how it failed.

Thanks to planetgeek for a nice article about writing custom hamcrest matchers.

Comments