Reputation: 8672
Here are my requirements for unit testing:
This seems like reasonable requirements. However, a problem always arises when I need to use methods such as assertEquals
on objects as these requires that the equals
method is overridden. The equals
method would have to be implemented in production classes but is actually only used for testing. This becomes even worse when good coding practices dictates that if equals
is overridden, then should hashCode
also be implemented resulting in even more unused production code that clutters the production classes.
Here is a simple example with a User
model (IntelliJ autoimplemented equals
and hashCode
)
public class User
{
public long id;
public long companyId;
public String name;
public String email;
public long version;
@Override
public boolean equals(Object o)
{
if(this == o) return true;
if(o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if(companyId != user.companyId) return false;
if(id != user.id) return false;
if(version != user.version) return false;
if(!email.equals(user.email)) return false;
if(!name.equals(user.name)) return false;
return true;
}
@Override
public int hashCode()
{
int result = (int) (id ^ (id >>> 32));
result = 31 * result + (int) (companyId ^ (companyId >>> 32));
result = 31 * result + name.hashCode();
result = 31 * result + email.hashCode();
result = 31 * result + (int) (version ^ (version >>> 32));
return result;
}
}
As it can be seen, equals
and hashCode
takes up a lot of space and clutters the class.
One solution to the problem could be to create a class, UserTester
, which could have an assertUserEquals
method that could be used instead of eg. JUnit's assertEquals
.
Another solution could be to create a UserComparator
. However, it does not seem like JUnit have any assertEquals
that takes a Comparator
.
What is best practices on this point?
Upvotes: 11
Views: 3013
Reputation: 417
You can use AssertJ and its Recursive comparison functionality
// assertion succeeds as the data of both objects are the same.
assertThat(actualSherlock)
.usingRecursiveComparison()
.isEqualTo(expectedSherlock);
Thanks to this library, you can check actual object equality with expected objects without any "test only" equals() and hashcode() overridings.
And reserve equals() overrides for real business use cases.
Upvotes: 1
Reputation: 11443
Uniutils has a perfect reflection equals method you can use for unit testing. This way your production code remains clear from all this test stuff.
public class User {
private long id;
private String first;
private String last;
public User(long id, String first, String last) {
this.id = id;
this.first = first;
this.last = last;
}
}
Later in test:
User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
assertReflectionEquals(user1, user2);
If you're using Mockito it has it's own means to do the same thing:
Mockito.verify(userDeleter).delete(Mockito.refEq(user));
Upvotes: 2
Reputation: 1117
I see two different things:
Sometimes it's not desirable to override hashcode&equals. It can change behavior of your program and it can hurt your performance, see Java Overriding hashCode() method has any Performance issue?
If there is no customer requirement to override hashcode&equals, like it's value object, you would not do that. Your should provide such code which exactly meets customer criteria, not more. Your test should deal with original implementation in default object.
Upvotes: 1
Reputation: 9741
Not the most efficient but one possible way is to compare fields using reflection.
public class Foo {
int x;
Foo(int in){
x = in;
}
public static void main(String[] args) throws Exception{
Foo o1 = new Foo(1),o2= new Foo(1);
boolean allMatch = true;
Class<?> c = Class.forName("Foo");
Field[] fields = c.getDeclaredFields();
for(Field f: fields){
allMatch &= f.get(o1)==f.get(o2);
}
}
}
Upvotes: 1