Skip to content
Chapter 05 · JUnit 5 & Mockito

Assertions and Testing Exceptions ​

Chapter 4 ended with the AAA pattern.

A test follows the AAA pattern: arrange the objects, act by calling the method, assert the outcome. Assertions verify the expected outcome; if an assertion fails, the test fails.

All of them are static methods of org.junit.jupiter.api.Assertions:

java
import static org.junit.jupiter.api.Assertions.*;

Every assertion also accepts an optional failure message as its last argument.

assertEquals ​

Checks that two values are equal; the test passes if expected and actual match.

java
assertEquals(expected, actual);
assertEquals(expected, actual, failureMessage);
java
class MyServiceTest {

    @Test
    void testMultiply() {
        // arrange
        MyService obj = new MyService();

        // act
        int output = obj.multiply(5, 6);

        // assert
        assertEquals(30, output, "both are not equal");
    }
}

If output were 31, the failure would read "both are not equal ==> expected: <30> but was: <31>".

For primitives it compares with ==; for objects with .equals(). assertEquals("hello", new String("hello")) passes, because String overrides equals.

For your own classes, override equals (and hashCode). Otherwise Object.equals is used, which checks reference equality: two different objects with the same values are not equal.

java
public class MyService {
    String name;
    public MyService(String name) { this.name = name; }
}

assertEquals(new MyService("XYZ"), new MyService("XYZ"));   // FAILS: two different objects
java
public class MyService {
    String name;

    public MyService(String name) { this.name = name; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof MyService)) return false;
        MyService other = (MyService) o;
        return Objects.equals(this.name, other.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}

assertEquals(new MyService("XYZ"), new MyService("XYZ"));   // passes

Two more traps:

  • Floating point. assertEquals(0.3, 0.1 + 0.2) fails ("expected: <0.3> but was: <0.30000000000000004>"). Pass a tolerance: assertEquals(0.3, 0.1 + 0.2, 1e-9).
  • Different boxed types. assertEquals(Integer.valueOf(5), Long.valueOf(5)) fails: an Integer never equals a Long.

assertNotEquals ​

The opposite: passes if the values are not equal. assertNotEquals(11, obj.multiply(5, 6)) passes.

Arrays and collections ​

assertArrayEquals: both arrays contain the same elements in the same order (element by element, == for primitives, .equals() for objects).

java
int[] actual = {3, 2, 1};
Arrays.sort(actual);
assertArrayEquals(new int[]{1, 2, 3}, actual);   // passes

assertIterableEquals: the same for Iterables such as List or Queue, compared element by element with .equals() (collections hold objects only). What must match is the iteration order: a PriorityQueue built from 3, 1, 2 iterates in its own order, not the insertion order. Avoid it for a Set, which guarantees no order.

java
assertIterableEquals(List.of("a", "b"), new ArrayList<>(List.of("a", "b")));   // passes
assertIterableEquals(List.of("a", "b"), List.of("b", "a"));
// fails: iterable contents differ at index [0], expected: <a> but was: <b>

assertLinesMatch(List<String> expected, List<String> actual): each element is a "line". An expected line matches if it is equal to the actual line or, failing that, if it matches it as a regular expression:

java
assertLinesMatch(List.of("Hello", "World from .*"),
                 List.of("Hello", "World from XYZ"));      // passes

assertLinesMatch(List.of("Hello", "[A-Za-z]+"),
                 List.of("Hello", "123"));
// fails: expected line #2 doesn't match actual line #2

Useful patterns: \\d+ digits, [A-Za-z]+ letters, \\w+ letters, digits and _, .* anything. An expected line like >>>> (or >> 2 >> for exactly two lines) fast-forwards over actual lines you do not care about.

References, null and booleans ​

AssertionPasses when
assertSame(a, b)a and b are the same object (same reference)
assertNotSame(a, b)they are different objects
assertNull(x)x is null
assertNotNull(x)x is not null
assertTrue(condition)the condition is true
assertFalse(condition)the condition is false
java
String o1 = "JUnit";              // from the string pool
String o2 = "JUnit";              // the same pooled object
String o3 = new String("JUnit");  // a new object

assertSame(o1, o2);   // passes
assertSame(o1, o3);   // fails: equal text, different objects

fail ​

Fails the test on purpose. Use it where the flow should never arrive:

java
try {
    obj.methodThrowsException();
    fail("above method should throw an exception");   // reached only if nothing was thrown
} catch (Exception e) {
    // expected
}

assertInstanceOf ​

Checks that an object is an instance of a class or one of its subclasses:

java
Number n = Integer.valueOf(10);
assertInstanceOf(Number.class, n);   // passes: Integer is a Number
assertInstanceOf(Double.class, n);   // fails: Unexpected type, expected: <java.lang.Double> but was: <java.lang.Integer>

Exceptions ​

assertThrows ​

java
assertThrows(Class<T> expectedType, Executable executable)

Passes if the code throws the expected exception or any subclass of it.

java
assertThrows(Exception.class, () -> Integer.parseInt("abc"));               // passes: NumberFormatException is an Exception
assertThrows(NumberFormatException.class, () -> Integer.parseInt("abc"));   // passes: the exact type
assertThrows(NullPointerException.class, () -> Integer.parseInt("abc"));
// fails: Unexpected exception type thrown, expected: <java.lang.NullPointerException> but was: <java.lang.NumberFormatException>

If nothing is thrown, it fails with "Expected java.lang.Exception to be thrown, but nothing was thrown."

Why a lambda? Executable is a functional interface. If you called Integer.parseInt("abc") directly as an argument, it would throw before assertThrows even ran. Passing the code as a lambda lets JUnit run it inside its own try/catch and compare what was caught.

assertThrows returns the exception, so you can check its details:

java
NumberFormatException ex = assertThrows(NumberFormatException.class, () -> Integer.parseInt("abc"));
assertEquals("For input string: \"abc\"", ex.getMessage());

assertThrowsExactly ​

The exception must be exactly that type; subclasses do not count.

java
assertThrowsExactly(IllegalArgumentException.class, () -> Integer.parseInt("abc"));
// fails: NumberFormatException is a subclass, not the exact type
assertThrowsExactly(NumberFormatException.class, () -> Integer.parseInt("abc"));   // passes

assertDoesNotThrow ​

The code must not throw; any exception fails the test.

java
assertDoesNotThrow(() -> Integer.parseInt("123"));   // passes

Timeouts ​

assertTimeout ​

java
assertTimeout(Duration timeout, Executable executable)

The code must finish within the time, or the test fails. But it runs on the same thread as the test and is not stopped: it runs to the end, and only then is the time checked.

java
assertTimeout(Duration.ofMillis(100), () -> Thread.sleep(500));
// fails after about 500 ms: execution exceeded timeout of 100 ms by 400 ms

assertTimeoutPreemptively ​

Same check, but execution is aborted as soon as the timeout passes. It runs the code on a different thread and interrupts it:

java
assertTimeoutPreemptively(Duration.ofMillis(100), () -> Thread.sleep(500));
// fails after about 100 ms: execution timed out after 100 ms

Because the code runs on another thread, anything bound to the test's thread (a ThreadLocal, such as a Spring-managed transaction) is not visible inside it.

assertAll ​

java
assertAll(Executable... executables)

Groups several assertions, runs all of them, and reports every failure together, instead of stopping at the first one.

java
String s = "";
assertAll(
    () -> assertEquals(6, s.length()),
    () -> assertTrue(s.startsWith("J")),
    () -> assertFalse(s.isEmpty())
);
text
Multiple Failures (3 failures)
    expected: <6> but was: <0>
    expected: <true> but was: <false>
    expected: <false> but was: <true>

Next: Chapters 4 and 5 used JUnit's built-in features. Chapter 6 shows how to add your own behavior to tests with extensions, the mechanism much of JUnit itself (and Mockito's integration) is built on.

Check yourself ​

  1. How does assertEquals compare primitives, and how objects?
  2. Why can assertEquals(new MyService("XYZ"), new MyService("XYZ")) fail, and how do you fix it?
  3. Why does assertEquals(0.3, 0.1 + 0.2) fail?
  4. What must match for assertIterableEquals to pass? Why avoid it for a Set?
  5. How does assertLinesMatch decide that a line matches?
  6. String a = "JUnit"; String b = new String("JUnit"); Does assertEquals(a, b) pass? assertSame(a, b)?
  7. When would you use fail()?
  8. Why does assertThrows take a lambda instead of the method call?
  9. The code throws NumberFormatException. Does assertThrows(IllegalArgumentException.class, ...) pass? assertThrowsExactly(IllegalArgumentException.class, ...)?
  10. A 100 ms timeout, code that sleeps 500 ms: how long does each timeout assertion take to fail, and why?
  11. What does assertAll do differently from three separate assertions?
Answers
  1. Primitives with ==; objects with .equals().
  2. Without an overridden equals, Object.equals compares references, and these are two different objects. Override equals and hashCode.
  3. 0.1 + 0.2 is 0.30000000000000004 in binary floating point; use the overload with a tolerance.
  4. The elements, compared with .equals(), in the same iteration order; a Set guarantees no order.
  5. An expected line matches if it is equal to the actual line, or else matches it as a regex (>> >> lines fast-forward).
  6. Yes (equal text). No (different objects).
  7. At a point the code should never reach, e.g. after a call that must throw.
  8. So JUnit can run the code inside its own try/catch; a direct call would throw before assertThrows runs.
  9. Yes (NumberFormatException is a subclass). No (not the exact type).
  10. assertTimeout: about 500 ms (same thread, runs to the end). assertTimeoutPreemptively: about 100 ms (another thread, interrupted).
  11. It runs every assertion and reports all failures together instead of stopping at the first.