Mark Needham

Thoughts on Software Development

Testing Hibernate mappings: Testing Equality

with 2 comments

I started a mini Hibernate series with my last post where I spoke of there being three main areas to think about when it comes to testing:

  1. Where to test the mappings from?
  2. How to test for equality?
  3. How to setup the test data?

Once we have worked out where to test the mappings from, if we have decided to test them through either our repository tests or directly from the Hibernate session then we have some choices to make around how to test for equality.

I’ve seen this done in several ways:

Override equals

This was the first approach I saw and in a way it does make some sort of sense to test like this.

We don’t have to expose any of the internals of the class and we can get feedback on whether our objects have the same fields values or not. In addition we can normally get the IDE to generate the code for the equals method so it doesn’t require much extra effort on our behalf.

Typically an equality test along these lines would look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Entity
public class Foo {
	@Column(name="BAR")
	private String bar;
 
	public Foo(String bar) {
		this.bar = bar;
	}
 
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
 
        Foo foo = (Foo) o;
 
        return !(bar != null ? !bar.equals(foo.bar) : foo.bar != null);
 
    }
}
1
2
3
4
5
6
import static org.hamcrest.MatcherAssert.assertThat;
...
Foo expectedFoo = new Foo("barValue");
Foo foo = getFooFromHibernate();
 
assertThat(foo, equalTo(expectedFoo));

The problem with this approach is that objects which are in Hibernate are likely to be entities and therefore their equality really depends on whether or not they have the same identity, not whether they have the same values. Therefore our equals method on the object should only compare the id value of the object to determine equality.

Implementing the equals method just for testing purposes may also be considered a code smell.

Getters

This approach involves adding getters to our objects to check that the values of each field have been set correctly.

While this approach is marginally better than not testing the mappings at all, the temptation to then use these getters in other pieces of the code can lead to us having objects with no behaviour at all with our logic spread all over the application.

1
2
3
4
5
6
7
8
9
10
11
12
13
@Entity
public class Foo {
	@Column(name="BAR")
	private String bar;
 
	public Foo(String bar) {
		this.bar = bar;
	}
 
	public getBar() {
		return this.bar;
	}
}
1
2
3
4
5
6
import static org.hamcrest.MatcherAssert.assertThat;
...
String bar = "barValue";
Foo foo = getFooFromHibernate();
 
assertThat(foo.getBar(), equalTo(bar));

Reflection

An approach I was introduced to recently involves using reflection to check that Hibernate has hydrated our objects correctly.

We initially rolled our own ‘Encapsulation Breaker’ to achieve this before realising that the OGNL library did exactly what we wanted to do.

By adding a custom Hamcrest matcher into the mix we end up with quite a nice test for verifying whether our mappings are working correctly.

1
2
3
4
5
@Entity
public class Foo {
	@Column(name="BAR")
	private String bar;
}
1
2
3
4
import static org.hamcrest.MatcherAssert.assertThat;
...
Foo foo = getFooFromHibernate();
assertThat(foo, hasMapping("bar", equalTo("someValue")));
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
31
public class HasMapping<T> extends BaseMatcher<T> {
    private String mapping;
    private Matcher<T> mappingValueMatcher;
 
    public HasMapping(String mapping, Matcher<T> mappingValueMatcher) {
        this.mapping = mapping;
        this.mappingValueMatcher = mappingValueMatcher;
    }
 
    public void describeTo(Description description) {
        description.appendText("A mapping from ");
        description.appendText(mapping);
        description.appendText(" that matches ");
        valueMatcher.describeTo( description );
    }
 
    @Factory
    public static <T> HasMapping hasMapping(String mapping, Matcher<T> mappingValueMatcher) {
        return new HasMapping(mapping, mappingValueMatcher);
    }
 
    public boolean matches(Object o) {
        try {
            Object value = OgnlWrapper.getValue(mapping, o);
            return mappingValueMatcher.matches(value);
        } catch (OgnlException e) {
            return false;
        }
    }
 
}

The drawback of this approach is that if we change the names of the fields on our objects we need to make a change to our test to reflect the new names.

I ran into the example dilemma a bit while writing this but hopefully the ideas have been expressed in the code presented. I didn’t want to put too much code in this post but if you’re interested in what the OgnlWrapper does I posted more about this on my post about Java checked exceptions.

Be Sociable, Share!

Written by Mark Needham

October 29th, 2008 at 6:03 pm

Posted in Hibernate,Testing

Tagged with ,

  • I agree that equality for persisted domain objects should be based on identity and not values. The primary key and class type is usually all you need to do this.

    Now, value equality does come in handy. I tend to implement a valuesEquals() method for this, and use something like the Apache Commons equals builder (in java) so that I’m not writing reflection code myself.

  • Pingback: Testing Hibernate mappings: Setting up test data at Mark Needham()