Appearance
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 objectsjava
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")); // passesTwo 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: anIntegernever equals aLong.
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); // passesassertIterableEquals: 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 #2Useful 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
| Assertion | Passes 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 objectsfail
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")); // passesassertDoesNotThrow
The code must not throw; any exception fails the test.
java
assertDoesNotThrow(() -> Integer.parseInt("123")); // passesTimeouts
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 msassertTimeoutPreemptively
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 msBecause 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
- How does
assertEqualscompare primitives, and how objects? - Why can
assertEquals(new MyService("XYZ"), new MyService("XYZ"))fail, and how do you fix it? - Why does
assertEquals(0.3, 0.1 + 0.2)fail? - What must match for
assertIterableEqualsto pass? Why avoid it for aSet? - How does
assertLinesMatchdecide that a line matches? String a = "JUnit"; String b = new String("JUnit");DoesassertEquals(a, b)pass?assertSame(a, b)?- When would you use
fail()? - Why does
assertThrowstake a lambda instead of the method call? - The code throws
NumberFormatException. DoesassertThrows(IllegalArgumentException.class, ...)pass?assertThrowsExactly(IllegalArgumentException.class, ...)? - A 100 ms timeout, code that sleeps 500 ms: how long does each timeout assertion take to fail, and why?
- What does
assertAlldo differently from three separate assertions?
Answers
- Primitives with
==; objects with.equals(). - Without an overridden
equals,Object.equalscompares references, and these are two different objects. OverrideequalsandhashCode. 0.1 + 0.2is0.30000000000000004in binary floating point; use the overload with a tolerance.- The elements, compared with
.equals(), in the same iteration order; aSetguarantees no order. - An expected line matches if it is equal to the actual line, or else matches it as a regex (
>> >>lines fast-forward). - Yes (equal text). No (different objects).
- At a point the code should never reach, e.g. after a call that must throw.
- So JUnit can run the code inside its own try/catch; a direct call would throw before
assertThrowsruns. - Yes (
NumberFormatExceptionis a subclass). No (not the exact type). assertTimeout: about 500 ms (same thread, runs to the end).assertTimeoutPreemptively: about 100 ms (another thread, interrupted).- It runs every assertion and reports all failures together instead of stopping at the first.