Question

Creating tests for an assignment, I'm getting an strange AssertionError exception.

I have changed it until i got to a simple case:

List<Integer> elements= new ArrayList<Integer>();
elements.add(1);
elements.add(2);
elements.add(3);

Permutation p2 = new Permutation(elements);
Permutation p1 = new Permutation(elements);

assertThat(p2, equalTo(p1));

Permutation.java:

public class Permutation {

  private List<Integer> elements;

  public Permutation(List<Integer> elements) {
    this.elements = elements;
  }

public boolean equals(Permutacion permutation){
  if ( this.elements.size() != permutation.elements.size()){
    return false;
  }

  Iterator<Integer> iterThis = this.elements.iterator();
  Iterator<Integer> iterOther = permutation.elements.iterator();
  while ( iterThis.hasNext() && iterOther.hasNext()){
    if ( iterThis.next() != iterOther.next()){
      return false;
    }
  }

  return true;

}

Digging in both junit and hamcrest source code I have found that junit assertThat only calls matches on the matcher.

The matches method in this case is:

public boolean matches(Object arg) {
    return areEqual(arg, object);
}

private static boolean areEqual(Object o1, Object o2) {
    if (o1 == null) {
        return o2 == null;
    } else if (o2 != null && isArray(o1)) {
        return isArray(o2) && areArraysEqual(o1, o2);
    } else {
        return o1.equals(o2);
    }
}

Where arg should be "p2" and object should be "p1".

(It can be browsed at Hamcrest repository)

The results of the comparisons in areEqual method, using debugger inspection, are:

"p2 == null"                    false   
"p1 != null"                    true    
"p2.getClass().isArray()"       false   
"p2.equals(p1)"                 true    
"equalTo(p1).matches(p2)"       false   

So as you can see, the code should reach the last else condition and return true ( p2.equals(p1)), but equalTo(p1).matches(p2) returns false

Thanks for your help

Was it helpful?

Solution

Why would you expect p2.equals(p1) to return true? You haven't overridden equals in the Permutation class, so it will use reference identity by default. You need to override equals (and hashCode, in general, to match equals) to indicate what you mean by equality when it comes to permutations.

EDIT: Now you've posted more code, it's clearer what's going on. Your equals method has this signature:

public boolean equals(Permutacion permutation){

That doesn't override Object.equals, which is what the matcher will be using. Instead, it overloads it - introducing a new method, which is what is being called in the debugger inspection. If you write:

Object o1 = p1;

then p2.equals(o1) will show false in the debugger too - and that's effectively what the matcher is doing. Your equals method should look something like:

@Override
public boolean equals(Object other)
{
    if (other == null || other.getClass() != this.getClass())
    {
        return false;
    }
    Permutation otherPermutation = (Permutation) other;

    // List.equals should do the right thing here
    return elements.equals(otherPermutation.elements);
}

(You should also override hashCode in a way which corresponds to this.)

Additionally:

  • Either consider the case where elements is null, or validate it in the constructor
  • Equality is simplest to define in final classes
  • As you're not making a defensive copy of the list, it's possible for it to be mutated by the caller after construction; that could lead to problems if you ever use the permutation as a key in a map, for example.
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top