I am having a problem where some class X extends extends java.util.AbstractMap, and also overloads equals(Object). Calling org.testng.Assert.assertEquals(X a, X b) resolves to assertEquals(Map<?,?>, Map<?,?>). Instead of calling the ‘equals’ method, the map entries are compared. This results in assertEquals(a, b) passing even though a.equals(b) is false.
This code demonstrates the problem:
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import java.util.AbstractMap;
import java.util.Collections;
import java.util.Set;
import org.testng.annotations.Test;
public class AssertTest {
static class X extends AbstractMap<String, Object> {
private int i;
public X(int i) {
this.i = i;
}
@Override
public Set<Entry<String, Object>> entrySet() {
return Collections.EMPTY_SET;
}
@Override
public boolean equals(Object o) {
return o instanceof X && i == ((X)o).i;
}
@Override
public int hashCode() {
return i;
}
}
@Test
public void test() {
X one = new X(1);
X two = new X(2);
assertEquals(one, two); // passes, should fail IMO
assertTrue(one.equals(two)); // correctly fails
}
}
What is the best way to work around this problem? One possibility is to remember to not use assertEquals to verify that instances of X match, but that is extremely error-prone.
Another possibility is to simply make a local copy of testng and rename the overloads. That creates an ongoing maintenance problem.
The only other idea I have is to create a project-specific Assert class that delegates to testng.Assert but renames the problematic overloads to ‘assertCollectionEquals’, ‘assertMapEquals’, etc.
org.testng.Assert.assertEquals(X a, X b)calls theassertEquals(Map, Map)method which iterates over the entry set and checks that all entries are equals, bypassing theMap#equalsmethod, as you have noticed.A simple cast would avoid calling that method and would use the
X#equalsmethod instead:You could also declare your variables as objects to achieve the same result:
That does not realy solve your issue in the sense that it still is error prone.
To avoid the occasional error, there are a few workarounds I can think of (getting tired here so some might make little sense), using the fact that
assertEqualswithout cast callsx.entrySet():Xis declared on the left of an=@BeforeGroupsmethod (if you use groups) and put each test that relies on that idiom in the same group, so that they will use a mocked X where entrySet fails your tests (and the other methods work as expected), or at least logs/prints some warning. If you also need to useentrySetin the same method that won’t workTestNG#assertEquals(Map, Map)to get the desired behaviourassertEquals(Map, Map)method. If you have a static import of all assertEquals method you will have to reimplement the other signatures too 🙁assertEquals(Map, Map)method – same caveat