{"id":20490893,"url":"https://github.com/osoco/grails-equals-hashcode","last_synced_at":"2026-05-09T08:02:05.036Z","repository":{"id":1500364,"uuid":"1754066","full_name":"osoco/grails-equals-hashcode","owner":"osoco","description":"Grails plugin providing the Spock base specification to test the contract for equals and hashCode methods","archived":false,"fork":false,"pushed_at":"2013-02-07T17:18:12.000Z","size":572,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-01-16T05:26:13.302Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://grails.org/plugin/equals-hashcode-test","language":"Groovy","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/osoco.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2011-05-16T07:10:45.000Z","updated_at":"2013-11-27T08:26:29.000Z","dependencies_parsed_at":"2022-08-16T13:30:10.276Z","dependency_job_id":null,"html_url":"https://github.com/osoco/grails-equals-hashcode","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osoco%2Fgrails-equals-hashcode","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osoco%2Fgrails-equals-hashcode/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osoco%2Fgrails-equals-hashcode/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osoco%2Fgrails-equals-hashcode/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/osoco","download_url":"https://codeload.github.com/osoco/grails-equals-hashcode/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242068680,"owners_count":20066942,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-15T17:18:56.475Z","updated_at":"2026-05-09T08:02:04.974Z","avatar_url":"https://github.com/osoco.png","language":"Groovy","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Summary\nBase [Spock](http://code.google.com/p/spock/) specification for testing equals and hashCode methods if they are compliant with equals/hashCode contracts.\n\nAs of 0.3 version it's independent of the Spock and Grails Spock plugin [Grails Spock plugin](http://www.grails.org/plugin/spock) version\n\n# Usage\nWe have a domain class where we implemented ``equals`` and ``hashCode`` methods (either with Apache Commons Lang builders or with Groovy AST transformations). ``includedSampleProperty`` and ``child`` are used in ``equals``/``hashCode``, whereas ``ignoredSampleProperty`` is not a part of object's equality.\n\n```\nclass DomainObject {\n    String includedSampleProperty\n    String ignoredSampleProperty\n    SecondLevelDomainObject child\n\n    boolean equals(o) {\n        if (o == null) return false\n        if (this.is(o)) return true\n        if (!(o instanceof DomainObject)) return false\n\n        DomainObject that = (DomainObject) o\n        new EqualsBuilder()\n            .append(includedProperty, that.includedProperty)\n            .append(child, that.child)\n            .isEquals()\n    }\n\n    int hashCode() {\n        new HashCodeBuilder()\n            .append(includedProperty)\n            .append(child)\n            .toHashCode()\n    }\n}\n\nclass SecondLevelDomainObject {\n    String sampleProperty\n\n    boolean equals(o) { // ... }\n    int hashCode() { // ... }\n}\n```\n\nWe should test both ``equals`` and ``hashCode`` methods if they:\n\n* fulfill ``equals`` and ``hashCode`` contracts as specified in [Object](http://docs.oracle.com/javase/6/docs/api/java/lang/Object.html) Javadoc (see below)\n* use some properties in ``equals``/``hashCode``\n* ignore some properties (i.e. if their value change, and the remaining properties stay unchanged, ``equals`` and ``hashCode`` should return the same value as before change)\n\nTo keep your code DRY, extend ``EqualsHashCodeSpec`` (which in turn extends ``UnitSpec``) and:\n\n* override the factory method ``createDomainObjectToCompare`` that spawns a new object under test\n* override the method ``modifiedPropertiesIncludedInEqualsAndHashCode`` that returns a map of property names used in ``equals``/``hashCode`` and their values changed with respect to the object created by ``createDomainObjectToCompare``\n* optionally override the method ``modifiedPropertiesIgnoredInEqualsAndHashCode`` for properties NOT used in ``equals``/``hashCode``\n* you may use closures as property values for lazy evaluation (as for ``child`` property value in the example below)\n\nExample for our sample ``DomainObject``:\n\n```\nclass ChildFactory {\n    static newExtremelyComplexSecondLevelDomainObject() {\n        new SecondLevelDomainObject(sampleProperty: 'a value')\n    }\n}\n\nclass DomainObjectSpec extends EqualsHashCodeSpec {\n\n    def createDomainObjectToCompare() {\n        new DomainObject(includedSampleProperty: 'foo', ignoredSampleProperty: 'bar',\n            child: ChildFactory.newExtremelyComplexSecondLevelDomainObject())\n    }\n\n    def modifiedPropertiesIncludedInEqualsAndHashCode() {\n        [includedSampleProperty: 'foo changed', , child: {\n            def child = ChildFactory.newExtremelyComplexSecondLevelDomainObject()\n            child.sampleProperty = 'a different value'\n            child\n        }]\n    }\n\n    def modifiedPropertiesIgnoredInEqualsAndHashCode() {\n        [ignoredSampleProperty: 'bar changed']\n    }\n}\n```\n\n# How the plugin works\n``EqualsHashCodeSpec`` applies a *One Bad Attribute* pattern (variation of [Derived Value](http://xunitpatterns.com/Derived%20Value.html)). 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. \n\nTests 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). \n\nTests 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).\n\n# equals and hashCode contracts\nAccording to [Object](http://docs.oracle.com/javase/6/docs/api/java/lang/Object.html) Javadoc, ``equals``:\n\n* It is _reflexive_: for any non-null reference value ``x``, ``x.equals(x)`` should return ``true``.\n* It is _symmetric_: for any non-null reference values ``x`` and ``y``, ``x.equals(y)`` should return ``true`` if and only if ``y.equals(x)`` returns ``true``.\n* It is _transitive_: for any non-null reference values ``x``, ``y``, and ``z``, if ``x.equals(y)`` returns ``true`` and ``y.equals(z)`` returns ``true``, then ``x.equals(z)`` should return ``true``.\n* It is _consistent_: for any non-null reference values ``x`` and ``y``, multiple invocations of ``x.equals(y)`` consistently return ``true`` or consistently return ``false``, provided no information used in equals comparisons on the objects is modified. For any non-null reference value ``x``, ``x.equals(null)`` should return ``false``.\n\n``hashCode`` should be always implemented when equals is overriden and:\n\n* 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.\n* 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.\n* It is not required that if two objects are unequal according to the ``equals(java.lang.Object)`` method, then calling the ``hashCode`` 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.\n\nAs much as is reasonably practical, the hashCode method defined by class ``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.)\n\n# Changelog\n* 0.3\n\t* Indepenent of Spock and Grails Spock plugin version\n* 0.2-spock-0.5-groovy-1.7, 0.2-spock-0.6 version - March 06, 2012\n\t* Compatible with Grails 2.0\n\t* Two versions (for Grails 1.3.7 and Grails 2.0) following Spock versioning\n* 0.2.0 version - June 29, 2011\n\t* EqualsHashCodeSpec moved to es.osoco.grails.plugins package. Please update your test code after upgrading to this version\n\t* Lazy property value evaluation\n* 0.1.1 version - May 31, 2011\n\t* equals() is symmetric is tested correctly\n* 0.1 version - May, 2011\n\t* initial release\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fosoco%2Fgrails-equals-hashcode","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fosoco%2Fgrails-equals-hashcode","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fosoco%2Fgrails-equals-hashcode/lists"}