1 User Guide - Reference Documentation
Authors: Marcin Gryszko
Version: 0.2-spock-0.6
1 User Guide
This plugin provides a base Spock specification for testing equals and hashCode methods. You can use it to test both Grails domain classes and other Groovy objects.It depends on the Grails Spock plugin which in turn depends on Spock0.5-groovy-1.7
version.
1.1 How to use the plugin
We have a domain class where we implementedequals
and hashCode
methods (with Apache Commons Lang builders). includedSampleProperty
and child
are used in equals
/hashCode
, whereas ignoredSampleProperty
is not a part of object's equality.
class DomainObject { String includedSampleProperty String ignoredSampleProperty SecondLevelDomainObject child boolean equals(o) { if (o == null) return false if (this.is(o)) return true if (!(o instanceof DomainObject)) return false DomainObject that = (DomainObject) o new EqualsBuilder() .append(includedProperty, that.includedProperty) .append(child, that.child) .isEquals() } int hashCode() { new HashCodeBuilder() .append(includedProperty) .append(child) .toHashCode() } }class SecondLevelDomainObject { String sampleProperty boolean equals(o) { // … } int hashCode() { // … } }
equals
and hashCode
methods if they:
- fulfill
equals
andhashCode
contracts as specified inapi:java.lang.Object
Javadoc (see below) - use some properties in
equals
/hashCode
- ignore some properties (i.e. if their value change, and the remaining properties stay unchanged,
equals
andhashCode
should return the same value as before change)
EqualsHashCodeSpec
(which in turn extends UnitSpec
) and:
- override the factory method
createDomainObjectToCompare
that spawns a new object under test - override the method
modifiedPropertiesIncludedInEqualsAndHashCode
that returns a map of property names used inequals
/hashCode
and their values changed with respect to the object created bycreateDomainObjectToCompare
- optionally override the method
modifiedPropertiesIgnoredInEqualsAndHashCode
for properties NOT used inequals
/hashCode
- you may use closures as property values for lazy evaluation (as for
child
property value in the example below)
DomainObject
:
class ChildFactory { static newExtremelyComplexSecondLevelDomainObject() { new SecondLevelDomainObject(sampleProperty: 'a value') } }class DomainObjectSpec extends EqualsHashCodeSpec { def createDomainObjectToCompare() { new DomainObject(includedSampleProperty: 'foo', ignoredSampleProperty: 'bar', child: ChildFactory.newExtremelyComplexSecondLevelDomainObject()) } def modifiedPropertiesIncludedInEqualsAndHashCode() { [includedSampleProperty: 'foo changed', , child: { def child = ChildFactory.newExtremelyComplexSecondLevelDomainObject() child.sampleProperty = 'a different value' child }] } def modifiedPropertiesIgnoredInEqualsAndHashCode() { [ignoredSampleProperty: 'bar changed'] } }
1.2 How the plugin works
EqualsHashCodeSpec
applies a One Bad Attribute pattern (variation of Derived Value ). For each property used and ignored in equals
/hashCode
, createDomainObjectToCompare
creates two objects to compare. One of the objects is modified - a single property value is changed.Tests with changed properties used in equals
/hashCode
verify that two objects are not equal. They don't check if hashCode values of different objects are the same (although a good implementation of hashCode
should return distinct values for unequal objects).Tests with properties ignored in equals
/hashCode
verify that two objects are equal and hash codes are the same (although a property value is different in two objects).
1.3 equals and hashCode contracts
According toapi:java.lang.Object
Javadoc, equals
:It is consistent : for any non-null reference values
- It is reflexive : for any non-null reference value
x
,x.equals(x)
should returntrue
.- It is symmetric : for any non-null reference values
x
andy
,x.equals(y)
should returntrue
if and only ify.equals(x)
returnstrue
.- It is transitive : for any non-null reference values
x
,y
, andz
, ifx.equals(y)
returnstrue
andy.equals(z)
returnstrue
, thenx.equals(z)
should returntrue
.x
andy
, multiple invocations ofx.equals(y)
consistently returntrue
or consistently returnfalse
, provided no information used in equals comparisons on the objects is modified. For any non-null reference valuex
,x.equals(null)
should returnfalse
.
hashCode
should be always implemented when equals is overriden and:As much as is reasonably practical, the hashCode method defined by class
- Whenever it is invoked on the same object more than once during an execution of a Java application, the
hashCode
method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.- If two objects are equal according to the
equals(Object)
method, then calling the hashCode method on each of the two objects must produce the same integer result.- It is not required that if two objects are unequal according to the
equals(java.lang.Object)
method, then calling thehashCode
method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hashtables.Object
does return distinct integers for distinct objects. (This is typically implemented by converting the internal address of the object into an integer, but this implementation technique is not required by the JavaTM programming language.)