53

What is mechanism below that makes equal different types?

import static org.testng.Assert.assertEquals;
@Test
public void whyThisIsEqual() {
    assertEquals(new HashSet<>(), new ArrayList<>());
}
Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
Bartek
  • 2,109
  • 6
  • 29
  • 40

5 Answers5

56

The assertEquals(Collection<?> actual, Collection<?> expected) documentation says:

Asserts that two collections contain the same elements in the same order. If they do not, an AssertionError is thrown.

Thus the content of the collections will be compared which, in case both the collections are empty, are equal.

Aaron
  • 24,009
  • 2
  • 33
  • 57
S.K.
  • 3,597
  • 2
  • 16
  • 31
31

They are not...

System.out.println(new HashSet<>().equals(new ArrayList<>())); // false

This is specific to testng assertEquals

Looking at the documentation of that method it says:

Asserts that two collections contain the same elements in the same order.

And this is ridiculous to me, a Set does not have an order, per-se.

Set<String> set = new HashSet<>();
set.add("hello");
set.add("from");
set.add("jug");

System.out.println(set); // [from, hello, jug]

IntStream.range(0, 1000).mapToObj(x -> x + "").forEachOrdered(set::add);
IntStream.range(0, 1000).mapToObj(x -> x + "").forEachOrdered(set::remove);

System.out.println(set); // [jug, hello, from]

So comparing these against a Collection at some particular point in time would yield interesting results.

Even worse, java-9 Set::of methods implement a randomization internally, so the order (or not the order) will be different from run to run.

Lauren Rutledge
  • 1,195
  • 5
  • 18
  • 27
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • 5
    I find that a dangerous choice from junit to call `assetEquals` but not use `Object.equals`... this lead to "unexpected" behavior like this one. – AxelH Sep 06 '18 at 10:22
  • @AxelH indeed, I was really not expecting this – Eugene Sep 06 '18 at 10:23
  • 1
    Junit throws an `AssertionError` here. It is testng which has this behaviour. (I see you edited your answer right when I posted this comment) – marstran Sep 06 '18 at 10:24
  • @marstran see another edit, I find this a very very weird – Eugene Sep 06 '18 at 10:34
  • An easier way to change the order is to play with the capacity and load factor. – Peter Lawrey Sep 06 '18 at 10:56
  • Should probably report this to the testng people. They can't treat all collections as ordered. – JollyJoker Sep 06 '18 at 14:31
  • 4
    @AxelH & Eugene, I agree, this method is misnamed. This should be named something like assertEqualContentsOrdered. It would be useful for comparing a list and a set, in the case that you know that the set is ordered. (There is already assertEqualsNoOrder but it only takes arrays.) – Stuart Marks Sep 06 '18 at 16:37
  • Sets don't have an order, but there isn't any for the test framework to know this from the `Collections` interface. I'd argue that the test methods shouldn't have been implemented this way, really. For comparison, Python's unittest library's `assertEqual` is a simple `==` (akin to `equals` in Java) check. Then there is separate methods for things like `assertSequenceEqual`, `assertItemsEqual`, etc, which make it clearer how they are handling collections. – Kat Sep 10 '18 at 18:31
  • Oh, also, people are mentioning JUnit here. I initially thought it was that, too (I've never seen anyone use anything else in Java), but it's a totally different framework, some TestNG. JUnit does not have this issue. [There's no `Collection` type overrides](http://junit.sourceforge.net/javadoc/org/junit/Assert.html), so it should be using the `equals` method of the collection. – Kat Sep 10 '18 at 18:34
9

Testng calls through to a method implemented this way.

  public static void assertEquals(Collection<?> actual, Collection<?> expected, String message) {
    if (actual == expected) {
      return;
    }

    if (actual == null || expected == null) {
      if (message != null) {
        fail(message);
      } else {
        fail("Collections not equal: expected: " + expected + " and actual: " + actual);
      }
    }

    assertEquals(
        actual.size(),
        expected.size(),
        (message == null ? "" : message + ": ") + "lists don't have the same size");

    Iterator<?> actIt = actual.iterator();
    Iterator<?> expIt = expected.iterator();
    int i = -1;
    while (actIt.hasNext() && expIt.hasNext()) {
      i++;
      Object e = expIt.next();
      Object a = actIt.next();
      String explanation = "Lists differ at element [" + i + "]: " + e + " != " + a;
      String errorMessage = message == null ? explanation : message + ": " + explanation;
      assertEqualsImpl(a, e, errorMessage);
    }
  }

This is trying to be helpful but is poor for a number of reasons.

Two equals collections can appear to be different.

Set<Integer> a = new HashSet<>();
a.add(82);
a.add(100);
System.err.println(a);
Set<Integer> b = new HashSet<>();
for (int i = 82; i <= 100; i++)
    b.add(i);
for (int i = 83; i <= 99; i++)
    b.remove(i);
System.err.println(b);
System.err.println("a.equals(b) && b.equals(a) is " + (a.equals(b) && b.equals(a)));
assertEquals(a, b, "a <=> b");

and

Set<Integer> a = new HashSet<>();
a.add(100);
a.add(82);
System.err.println(a);
Set<Integer> b = new HashSet<>(32);
b.add(100);
b.add(82);
System.err.println(b);
System.err.println("a.equals(b) && b.equals(a) is " + (a.equals(b) && b.equals(a)));
assertEquals(a, b, "a <=> b");

prints

[82, 100]
[100, 82]
a.equals(b) && b.equals(a) is true
Exception in thread "main" java.lang.AssertionError: a <=> b: Lists differ at element [0]: 100 != 82
    at ....

Two collections can be the same or different depending on how they are compared.

assertEquals(a, (Iterable) b); // passes

assertEquals(a, (Object) b); // passes

assertEquals(Arrays.asList(a), Arrays.asList(b)); // passes
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
6

Because for collection only content is compared, not collection type.

Rationale behind it is that often some subclass of collection is returned from tested method and is irrelevant what exactly subclass is used.

talex
  • 17,973
  • 3
  • 29
  • 66
2

When I run below code the condition is false.

if( (new HashSet<>()).equals(new ArrayList<>())){
            System.out.println("They are equal");
        }

Hence for assertEquals, it is true that it only checks elements and its order for equality. But for equals it is false.

Raj
  • 707
  • 6
  • 23