skip to content

What happens if you pass two arrays to JUnit 5's assertEquals, and how do assertArrayEquals and assertIterableEquals differ from it and from each other?

level: middleimportance: must knowfreq 58%

answer

  1. arrays inherit Object.equals -> identity -> assertEquals fails
  2. assertArrayEquals: element-wise, recurses into nested arrays
  3. failure names index path [1][3]; double[] overload takes delta
  4. assertIterableEquals ignores collection type; ArrayDeque has no equals
  5. all of them are order-sensitive

basics

~20 s

Arrays inherit Object.equals, which is reference identity, so assertEquals on two distinct arrays fails even with identical contents. Use assertArrayEquals for element-wise, recursive array comparison, and assertIterableEquals to compare any two Iterables element-wise without requiring the same collection type.

solid answer

~50 s

Java arrays do not override `equals`, so `assertEquals(new int[]{1,2}, new int[]{1,2})` compares references and fails. JUnit 5 gives you dedicated assertions: - **`assertArrayEquals`** — element-wise, with overloads for every primitive array plus `Object[]`. It recurses into nested arrays (so `int[][]` works) and reports the failing position as an index path, e.g. `array contents differ at index [1][3]`. The `double[]`/`float[]` overloads accept a delta for floating-point tolerance. - **`assertIterableEquals`** — walks two `Iterable`s in order and compares each pair with `equals`, recursing into nested iterables. Crucially it does not require the two arguments to be the same collection class, and it does not call the collection's own `equals`. That is what makes it work for types like `ArrayDeque`, which inherits identity `equals`, or for comparing a `List` against any other ordered `Iterable`. Order matters in both; for order-insensitive comparison, sort first or compare as sets.

code

java · 18 lines
java
@Test
void arraysAndIterables() {
    int[] expected = {1, 2, 3};
    int[] actual = {1, 2, 3};

    // assertEquals(expected, actual);   // FAILS: arrays use identity equals
    assertArrayEquals(expected, actual); // passes, element-wise

    String[][] grid = {{"a", "b"}, {"c", "d"}};
    assertArrayEquals(new String[][] {{"a", "b"}, {"c", "d"}}, grid); // deep

    assertArrayEquals(new double[] {0.3}, new double[] {0.1 + 0.2}, 1e-9);

    // ArrayDeque does not override equals -> assertEquals would compare identity
    Deque<String> a = new ArrayDeque<>(List.of("x", "y"));
    Deque<String> b = new ArrayDeque<>(List.of("x", "y"));
    assertIterableEquals(a, b);
}

go deeper

for a junior

Know the trap and the fix: assertEquals on arrays compares references, so use assertArrayEquals. One example is enough.

for a middle

Add the mechanics — deep recursion into nested arrays, primitive overloads, the delta overloads for double[]/float[], and why assertIterableEquals exists for types whose equals is identity-based.

for a senior

Emphasise diagnostics and correctness of intent: choose the assertion whose failure message localises the defect, and never let iteration order of an unordered collection become part of the contract.

for a principal

Frame it as consistency across the suite: which comparison style is standard, whether an assertion library is warranted for deep object graphs, and the requirement that domain element types carry value-based equality.

## Arrays and equals In Java an array is an object whose class is generated by the JVM and which inherits `equals` straight from `Object`. `Object.equals` is reference identity. So two arrays with identical contents are never `equals` unless they are literally the same array. Since JUnit's `assertEquals(Object, Object)` delegates to `equals`, passing two arrays to it compares identities and fails with a confusing message showing two identical-looking `[1, 2, 3]` renderings. This is the single most common array assertion mistake, and it is why JUnit provides separate assertions rather than special-casing arrays inside `assertEquals`. ## assertArrayEquals `assertArrayEquals(expected, actual)` compares arrays element by element. There is an overload for each primitive array type (`boolean[]`, `byte[]`, `char[]`, `short[]`, `int[]`, `long[]`, `float[]`, `double[]`) and one for `Object[]`. Behaviour worth knowing: - **Length first.** If lengths differ, the assertion fails immediately with `array lengths differ, expected: <3> but was: <2>`. - **Deep by default for nested arrays.** The `Object[]` overload recurses when elements are themselves arrays, so a `String[][]` or `Object[]` containing `int[]` compares correctly all the way down. This mirrors `Arrays.deepEquals`, not `Arrays.equals`. - **Index path in the message.** Failure output names the exact position, including nesting: `array contents differ at index [2][0], expected: <x> but was: <y>`. That is a real diagnostic advantage over converting arrays to lists. - **Floating point.** `assertArrayEquals(double[] expected, double[] actual, double delta)` and the `float[]` equivalent apply a per-element tolerance. Without a delta, elements are compared with the same bitwise semantics as the scalar no-delta overload, so `NaN` matches `NaN` and `+0.0` does not match `-0.0`. - **Nulls.** Two null arrays pass; one null fails with a message naming which side was null. Null *elements* inside `Object[]` are handled null-safely. - **Self-referencing arrays** (an `Object[]` containing itself) are detected rather than causing infinite recursion. ## assertIterableEquals `assertIterableEquals(Iterable<?> expected, Iterable<?> actual)` iterates both arguments in parallel and compares corresponding elements with `equals`, recursing when an element pair is itself a pair of `Iterable`s. Two properties make it distinct from simply calling `assertEquals` on two collections: 1. **The collections' own `equals` is never consulted.** `assertEquals(list, otherCollection)` would call `List.equals`, whose contract requires the other object to also be a `List`. So comparing a `List` to a `Queue`, a `Deque`, or a custom `Iterable` fails on type grounds even when the elements match. `assertIterableEquals` sidesteps that. The classic case is `ArrayDeque`, which does **not** override `equals` at all — `assertEquals` on two `ArrayDeque`s with identical contents compares identities and fails, while `assertIterableEquals` passes. 2. **It works on anything iterable**, including lazily-produced sequences and custom domain iterables, without materialising them into a specific collection class. The cost is that it is order-sensitive and says nothing about set semantics: comparing two `HashSet`s with `assertIterableEquals` depends on iteration order and is a flaky test waiting to happen. For unordered comparison, either compare sets with `assertEquals` (`Set.equals` is order-independent) or sort into lists first. ## Choosing between them - Two arrays → `assertArrayEquals`. - Two ordered collections of the same well-behaved type (`List` vs `List`) → `assertEquals` works fine and gives the clearest message; `assertIterableEquals` is equally valid. - One side is an array, one a `List` → convert deliberately: `assertIterableEquals(expected, Arrays.asList(actual))` or `assertArrayEquals(expected, actual.toArray())`. Do not rely on any assertion bridging the two. - Types with identity `equals` (`ArrayDeque`, many custom `Iterable`s) → `assertIterableEquals`. - Two `Set`s where order is meaningless → `assertEquals`, because `Set.equals` is defined by membership. - Lists of `String` where some lines are variable or should be skipped → `assertLinesMatch`, which is a separate specialised assertion. ## Diagnostics and readability All three throw `AssertionFailedError`, and the quality of the failure message is a genuine selection criterion. `assertArrayEquals` reports an index path; `assertIterableEquals` reports the position and nests the message for nested iterables. Both are far more useful than a hand-rolled loop with `assertTrue`, which typically reports only `expected: <true> but was: <false>`. If you find yourself writing a loop of assertions over a collection, one of these built-ins — or `assertAll` if you want every element reported — is almost always better. ## Element equality still matters Both assertions ultimately compare elements with `equals` (or with primitive comparison). If the element type has no meaningful `equals` override, both assertions degrade to identity per element and will fail on logically-equal results. Give element types a value-based `equals` (records are the cheapest way) before reaching for a comparison assertion.

  • You need to compare a List<String> against a String[]. How do you assert that?
    Convert one side explicitly rather than hoping an assertion bridges the two representations: either `assertIterableEquals(expectedList, Arrays.asList(actualArray))` or `assertArrayEquals(expectedArray, actualList.toArray(new String[0]))`. Neither `assertArrayEquals` nor `assertIterableEquals` accepts a mixed pair, and `assertEquals` would compare a `List` to an array and fail on type. Making the conversion visible also documents which representation the test treats as canonical.
  • How do you assert that two collections contain the same elements when order is irrelevant?
    Either compare them as sets — `assertEquals(Set.copyOf(expected), Set.copyOf(actual))`, since `Set.equals` is defined by membership and ignores iteration order — or sort both into lists and compare. Note that the set approach loses duplicate counts, so if multiplicity matters, sort instead or compare frequency maps. `assertIterableEquals` is the wrong tool here because it is strictly positional.
  • Why does JUnit's array assertion recurse into nested arrays rather than comparing elements with equals only?
    Because a nested element is itself an array, and arrays inherit identity `equals`, a shallow comparison would fail for logically-equal nested structures — the same trap one level down. Recursing matches `Arrays.deepEquals` semantics and lets the failure message report a full index path such as `[2][0]`, which pinpoints the differing cell. JUnit also guards against self-referencing arrays so the recursion cannot loop forever.

saying these in an interview costs you the question

  • Believing assertEquals special-cases arrays and compares their contents.
  • Thinking assertArrayEquals is shallow and cannot handle two-dimensional arrays.
  • Using assertIterableEquals to compare two HashSets, making the test depend on iteration order.
  • Comparing double arrays without a delta and blaming JUnit when tiny rounding differences fail.
  • Hand-rolling a for-loop of assertTrue calls instead of using an assertion that reports the failing index.

context