Is object deep-compare possible with Spock Framework?
Solution 1:
Spock has no built-in mechanism for performing deep Object comparison, because defining object equality is out of scope of any testing framework. You can do a various things.
1. Both classes are Groovy classes
If both your classes (Person
and Address
) are Groovy classes you can generate equals
and hashCode
methods using @EqualsAndHashCode
annotation over both classes, like:
import groovy.transform.EqualsAndHashCode
import groovy.transform.TupleConstructor
import spock.lang.Specification
class PersonSpec extends Specification {
def "a person test"() {
setup:
def person1 = new Person("Foo", new Address("Bar"))
def person2 = new Person("Foo", new Address("Bar"))
expect:
person1 == person2
}
@TupleConstructor
@EqualsAndHashCode
static class Person {
String name
Address address
}
@TupleConstructor
@EqualsAndHashCode
static class Address {
String city
}
}
This is just a convenient alternative for implementing both methods in Groovy.
2. Both classes are Java classes
If you want to compare both objects with ==
operator then you will have to define equals
and hashCode
methods in both classes, something like:
public final class Person {
private final String name;
private final Address address;
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public Address getAddress() {
return address;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
if (name != null ? !name.equals(person.name) : person.name != null) return false;
return address != null ? address.equals(person.address) : person.address == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + (address != null ? address.hashCode() : 0);
return result;
}
static class Address {
private final String city;
public Address(String city) {
this.city = city;
}
public String getCity() {
return city;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Address address = (Address) o;
return city != null ? city.equals(address.city) : address.city == null;
}
@Override
public int hashCode() {
return city != null ? city.hashCode() : 0;
}
}
}
In this example both methods were defined using IntelliJ IDEA "Generate equals and hashCode" command.
3. I can use Lombok!
If you don't want to define both methods manually (because e.g. you have to remember to change them anytime you modify your class fields) then you can use Lombok's @EqualsAndHashCode
annotation that does something similar to Groovy's annotation, but can be applied to any Java class.
4. I want to keep default equals
and hashCode
methods
Well, in this case you can try various things:
-
You can try comparing both objects field-by-field, like:
class PersonSpec extends Specification { def "a person test"() { setup: def person1 = new Person("Foo", new Address("Bar")) def person2 = new Person("Foo", new Address("Bar")) expect: person1.name == person2.name and: person1.address.city == person2.address.city } @TupleConstructor static class Person { String name Address address } @TupleConstructor static class Address { String city } }
You can try using 3rd party tools like Unitils reflection assertion
-
That may sound bizarre, but you can compare JSON representation of both objects, something like:
import groovy.json.JsonOutput import groovy.transform.TupleConstructor import spock.lang.Specification class PersonSpec extends Specification { def "a person test"() { setup: def person1 = new Person("Foo", new Address("Bar")) def person2 = new Person("Foo", new Address("Bar")) expect: new JsonOutput().toJson(person1) == new JsonOutput().toJson(person2) } @TupleConstructor static class Person { String name Address address } @TupleConstructor static class Address { String city } }
Anyway, I would definitely suggest defining equals
and hashCode
in one way or another and simply use ==
operator. Hope it helps.
Solution 2:
You can take advantage of Groovy's succinct map comparison syntax:
person1.properties == person2.properties
That only works for simple flat objects, not nested ones. You could adapt it like so:
person1.properties << ['address': person1.address.properties] == person2.properties << ['address': person2.address.properties]
...but JSON solution is more elegant at that point.