How do JUnit 5's assertEquals, assertSame and assertArrayEquals behave when one or both arguments are null, and how should that shape the way you write null assertions?
answer
- both null -> passes everywhere; never NPE
- assertEquals: expected==null ? actual==null : expected.equals(actual)
- assertArrayEquals prints 'expected array was <null>'
- prefer assertNull/assertNotNull for intent + overloads
- assertEquals(1, aLong) fails: Integer.equals(Long) is false
basics
~20 sAll of them are null-safe: both arguments null passes, one null fails with a message naming which side was null — no NullPointerException. Still prefer assertNull and assertNotNull for null checks, because they state intent and avoid overload-resolution problems with a bare null literal.
solid answer
~40 s`assertEquals` does a null-safe check: if the expected value is null it passes only when the actual value is null, otherwise it calls `expected.equals(actual)` — so a null actual never throws, it just fails. `assertSame(null, null)` passes because `null == null`. `assertArrayEquals(null, null)` passes; one null side fails with `expected array was <null>` rather than an NPE. Null *elements* inside object arrays and iterables are compared null-safely too. Even so, write `assertNull(result)` and `assertNotNull(result)` rather than `assertEquals(null, result)`. Three reasons: the intent is explicit in the assertion name; the failure message is clearer; and a bare `null` literal can hit overload ambiguity or silently select an unintended overload. A related trap is boxed-type mismatch: `assertEquals(1, someLong)` fails because `Integer.equals(Long)` is false regardless of the numeric value.
code
java · 14 lines@Test
void nullAndBoxing() {
assertEquals(null, null); // passes, no NPE
assertSame(null, null); // passes
assertArrayEquals(null, null); // passes
// assertArrayEquals(new int[]{1}, null); // fails: actual array was <null>
assertNull(repository.findById("missing")); // states intent
assertNotNull(repository.findById("known"));
Long count = service.count(); // boxed Long
// assertEquals(1, count); // FAILS: Integer.equals(Long) is false
assertEquals(1L, count); // passes
}go deeper
Know that null arguments are handled safely — both null passes, one null fails — and that assertNull/assertNotNull are the idiomatic checks.
Explain the exact comparison rule, the array and iterable behaviour, and the overload/boxing hazards that make a bare null or an int literal misbehave.
Point at the false-positive risk: assertions that pass because both sides are null prove nothing, so tests must observe at least one non-null value, and Optional-returning APIs should be asserted as Optionals.
Discuss nullability as a design decision — Optional or non-null-by-default annotations at module boundaries — so that tests rarely need null assertions at all.
## Null is a value, not a crash JUnit 5's assertions in `org.junit.jupiter.api.Assertions` are deliberately null-tolerant, because a test asserting on a value that unexpectedly turns out to be null should report a readable failure, not blow up with a `NullPointerException` inside the assertion library. **`assertEquals(Object, Object)`** uses a null-safe comparison: if the expected reference is null, the assertion passes exactly when the actual reference is also null; otherwise it calls `expected.equals(actual)`, and a well-behaved `equals` returns false for a null argument (that is required by the `Object.equals` contract). So: - `assertEquals(null, null)` → passes - `assertEquals("a", null)` → fails, `expected: <a> but was: <null>` - `assertEquals(null, "a")` → fails, `expected: <null> but was: <a>` Note the asymmetry in which object's `equals` runs — the *expected* one. A class with a sloppy `equals` that returns true for null would make `assertEquals(badObject, null)` pass; that is a defect in the class, not in JUnit. **`assertSame` / `assertNotSame`** compare with `==`, so `assertSame(null, null)` passes and there is nothing to dereference — inherently null-safe. **`assertArrayEquals`** treats null arrays as a comparable state: both null passes, one null fails with a message that names which side was null (`expected array was <null>` / `actual array was <null>`). Inside `Object[]`, null elements are compared null-safely, and the failure message reports the index. **`assertIterableEquals`** behaves the same way for null iterables and null elements. ## Why assertNull and assertNotNull still exist Given that `assertEquals(null, x)` works, why prefer `assertNull(x)`? 1. **Intent.** `assertNull(result)` reads as "this must be absent". `assertEquals(null, result)` reads as a value comparison that happens to use null, and a reviewer must pause on it. 2. **Message quality.** `assertNull` produces `expected: <null> but was: <Order[id=7]>` with the assertion name in the stack trace, so the report says *what kind* of check failed. 3. **Overload hazards.** A bare `null` literal is typed as the null type and is applicable to several reference overloads. In JUnit 5 the object overload is normally selected without complaint, but in code that mixes generics, casts or its own overloaded helpers, a bare null can produce an ambiguous-method compile error or select an overload you did not intend, forcing casts like `assertEquals((Object) null, result)`. Using `assertNull` removes the question. 4. **Static analysis.** Nullability checkers and IDE inspections understand `assertNull`/`assertNotNull` and can narrow types afterwards; `assertEquals(null, x)` teaches them nothing. A practical corollary: `assertNotNull` is often written as a defensive prelude to further assertions (`assertNotNull(order); assertEquals(7, order.id());`). That is reasonable but often redundant — the following assertion would throw an NPE anyway. Prefer `assertNotNull` when null is a *meaningful* outcome to rule out, or group the checks with `assertAll` so a null does not stop the rest of the test from reporting. ## The neighbouring trap: boxed types While on the subject of arguments that silently do the wrong thing, the most common non-null equality surprise is numeric boxing. `assertEquals(1, service.count())` where `count()` returns `long` may resolve to the `(long, long)` overload and pass — but `assertEquals(1, someLongObject)` where the actual is a `Long` object resolves to `(Object, Object)`, boxes the literal to `Integer`, and fails: `Integer.equals(Long)` is false for every value, producing the baffling `expected: <1> but was: <1>`. JUnit softens this by appending the types when the rendered strings match, so you see the class names in the message. The fix is to make the literal's type match — `assertEquals(1L, actual)` — or to unbox explicitly. The same class of problem appears with `char` versus `int`, and with `BigDecimal`, where `equals` also compares scale so `new BigDecimal("1.0")` is not equal to `new BigDecimal("1.00")`. ## Writing null-aware tests well - Use `assertNull` / `assertNotNull` for presence checks; keep `assertEquals` for values. - When a method returns `Optional`, assert on the `Optional` (`assertTrue(result.isPresent())`, `assertEquals(Optional.of(x), result)`) rather than unwrapping and null-checking. - Do not write assertions that pass when everything is null — `assertEquals(expected.name(), actual.name())` succeeds when both names are null, which may mean the fixture never populated them. If null-vs-null is a plausible false positive, assert the value explicitly first. - When asserting a whole object graph, remember that a null field on both sides passes silently; a test that never observes a non-null value is not proving much.
- A test reports 'expected: <1> but was: <1>' and fails. What is going on?The two values render identically but are different types — almost always a boxed-number mismatch, such as an `int` literal compared against a `Long`, because `Integer.equals(Long)` is false for any value. JUnit detects the identical string rendering and appends type information to the message so you can see it. Fix it by matching the literal's type (`1L`) or by comparing primitives so the primitive overload is selected.
- Is assertNotNull before a chain of assertions worth writing?Usually only when null is a genuine possible outcome you want named in the report; otherwise the next assertion would throw a NullPointerException and fail the test anyway, just with a less descriptive message. A good middle ground is to state it once at the top of the test, or to wrap the group in `assertAll` so a null does not hide the remaining checks. Avoid sprinkling it before every dereference — it is noise.
saying these in an interview costs you the question
- Believing assertEquals throws a NullPointerException when the actual value is null.
- Writing assertEquals(null, x) as the standard idiom instead of assertNull, losing intent and message quality.
- Assuming assertArrayEquals(null, null) fails, or that a null array is an error rather than a comparable state.
- Not knowing that assertEquals calls equals on the expected argument, so which side is null changes which code runs.
- Writing field-by-field assertions that pass because both sides are null and calling the object 'verified'.