Assertions in JUnit 5: Complete Guide with Examples

JUnit 5 provides a rich set of assertion methods to test expected conditions in tests. These methods are part of the org.junit.jupiter.api.Assertions class.

1. Basic Assertions

Simple Value Assertions

import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class BasicAssertionsTest { @Test void basicAssertions() { // assertEquals - checks equality assertEquals(2, 1 + 1); assertEquals("Hello", "Hello"); // assertNotEquals - checks inequality assertNotEquals(3, 1 + 1); assertNotEquals("Hello", "World"); // assertTrue - checks boolean condition assertTrue(5 > 3); assertTrue("Hello".startsWith("H")); // assertFalse - checks false condition assertFalse(5 < 3); assertFalse("Hello".endsWith("x")); // assertNull - checks for null String nullString = null; assertNull(nullString); // assertNotNull - checks for non-null String notNullString = "Hello"; assertNotNull(notNullString); } @Test void withMessages() { int expected = 4; int actual = 2 + 2; // Assertions with custom failure messages assertEquals(expected, actual, "2 + 2 should equal 4"); assertTrue(actual > 0, () -> "The result should be positive"); } }

2. Array and Iterable Assertions

Array Assertions

import java.util.Arrays; import java.util.List; class ArrayAssertionsTest { @Test void arrayAssertions() { String[] expectedArray = {"Java", "JUnit", "Testing"}; String[] actualArray = {"Java", "JUnit", "Testing"}; // Assert array equality assertArrayEquals(expectedArray, actualArray); // With message assertArrayEquals( new int[]{1, 2, 3}, new int[]{1, 2, 3}, "Arrays should be equal" ); // Multi-dimensional arrays int[][] expectedMatrix = {{1, 2}, {3, 4}}; int[][] actualMatrix = {{1, 2}, {3, 4}}; assertArrayEquals(expectedMatrix, actualMatrix); } @Test void iterableAssertions() { List<String> expectedList = Arrays.asList("Apple", "Banana", "Cherry"); List<String> actualList = Arrays.asList("Apple", "Banana", "Cherry"); // Assert iterable equality assertIterableEquals(expectedList, actualList); // Different order - this would fail // assertIterableEquals(expectedList, Arrays.asList("Banana", "Apple", "Cherry")); } @Test void linesMatchAssertion() { List<String> expectedLines = Arrays.asList("Java", ".*Unit", "Testing"); List<String> actualLines = Arrays.asList("Java", "JUnit", "Testing"); // linesMatch checks if actual lines match expected patterns assertLinesMatch(expectedLines, actualLines); } }

3. Object and Class Assertions

Object Assertions

class ObjectAssertionsTest { static class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } // equals and hashCode implemented for proper comparison @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); } @Override public int hashCode() { return Objects.hash(name, age); } } @Test void objectAssertions() { Person person1 = new Person("John", 30); Person person2 = new Person("John", 30); Person person3 = new Person("Jane", 25); // Assert object equality (uses equals() method) assertEquals(person1, person2); assertNotEquals(person1, person3); // Assert same instance assertSame(person1, person1); // Same object assertNotSame(person1, person2); // Different objects with same content } @Test void instanceOfAssertions() { Object stringObj = "Hello"; Object numberObj = 42; // Check if object is instance of specific class assertInstanceOf(String.class, stringObj); assertInstanceOf(Integer.class, numberObj); assertInstanceOf(Number.class, numberObj); // Integer is a Number // With message assertInstanceOf( String.class, stringObj, "Object should be a String" ); } }

4. Exception Assertions

Testing Exceptions

class ExceptionAssertionsTest { static class Calculator { public double divide(int a, int b) { if (b == 0) { throw new IllegalArgumentException("Cannot divide by zero"); } return (double) a / b; } public void validateAge(int age) { if (age < 0 || age > 150) { throw new IllegalArgumentException("Invalid age: " + age); } } } @Test void assertThrowsException() { Calculator calculator = new Calculator(); // Assert that specific exception is thrown IllegalArgumentException exception = assertThrows( IllegalArgumentException.class, () -> calculator.divide(10, 0) ); // Additional assertions on the exception assertEquals("Cannot divide by zero", exception.getMessage()); } @Test void assertThrowsWithMessage() { Calculator calculator = new Calculator(); // With custom message assertThrows( IllegalArgumentException.class, () -> calculator.validateAge(-5), "Should throw exception for negative age" ); } @Test void assertDoesNotThrow() { Calculator calculator = new Calculator(); // Assert that no exception is thrown assertDoesNotThrow(() -> calculator.divide(10, 2)); assertDoesNotThrow(() -> calculator.validateAge(25)); // With return value double result = assertDoesNotThrow(() -> calculator.divide(10, 2)); assertEquals(5.0, result); } @Test void assertAllWithExceptions() { Calculator calculator = new Calculator(); // Group multiple assertions assertAll( "Multiple exception tests", () -> assertThrows( IllegalArgumentException.class, () -> calculator.divide(10, 0) ), () -> assertDoesNotThrow(() -> calculator.divide(10, 2)), () -> { double result = calculator.divide(10, 2); assertEquals(5.0, result); } ); } }

5. Timeout Assertions

Testing Time-sensitive Code

class TimeoutAssertionsTest { @Test void timeoutAssertion() { // Assert that execution completes within timeout assertTimeout( java.time.Duration.ofSeconds(2), () -> { // Simulate some work Thread.sleep(1000); assertEquals(4, 2 + 2); } ); } @Test void timeoutPreemptively() { // Preemptive timeout - aborts execution if timeout exceeded assertTimeoutPreemptively( java.time.Duration.ofMillis(500), () -> { // This would be aborted if it takes too long int result = performCalculation(); assertEquals(42, result); } ); } @Test void timeoutWithReturnValue() { String result = assertTimeout( java.time.Duration.ofSeconds(1), () -> { Thread.sleep(100); return "Completed"; } ); assertEquals("Completed", result); } private int performCalculation() { try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return 42; } }

6. Grouped Assertions with assertAll()

Multiple Assertions Together

class GroupedAssertionsTest { static class User { private String username; private String email; private boolean active; private int loginCount; public User(String username, String email, boolean active, int loginCount) { this.username = username; this.email = email; this.active = active; this.loginCount = loginCount; } // Getters public String getUsername() { return username; } public String getEmail() { return email; } public boolean isActive() { return active; } public int getLoginCount() { return loginCount; } } @Test void assertAllGrouping() { User user = new User("john_doe", "[email protected]", true, 5); // Group multiple assertions - all are executed even if some fail assertAll("User properties", () -> assertEquals("john_doe", user.getUsername()), () -> assertEquals("[email protected]", user.getEmail()), () -> assertTrue(user.isActive()), () -> assertEquals(5, user.getLoginCount()) ); } @Test void nestedAssertAll() { User user = new User("jane_doe", "[email protected]", true, 10); assertAll("User validation", () -> assertAll("Username validation", () -> assertNotNull(user.getUsername()), () -> assertTrue(user.getUsername().length() >= 3), () -> assertFalse(user.getUsername().contains(" ")) ), () -> assertAll("Email validation", () -> assertNotNull(user.getEmail()), () -> assertTrue(user.getEmail().contains("@")), () -> assertTrue(user.getEmail().endsWith(".com")) ), () -> assertAll("Activity validation", () -> assertTrue(user.isActive()), () -> assertTrue(user.getLoginCount() > 0) ) ); } @Test void assertAllWithSupplierMessages() { User user = new User("test", "[email protected]", false, 0); assertAll( () -> assertEquals("test", user.getUsername(), () -> "Username should be 'test'"), () -> assertTrue(user.getEmail().contains("@"), () -> "Email should contain '@'"), () -> assertFalse(user.isActive(), () -> "User should be inactive initially") ); } }

7. Custom Assertions with AssertJ Integration

Using AssertJ with JUnit 5

import org.assertj.core.api.Assertions; class AssertJIntegrationTest { @Test void assertJBasicAssertions() { String name = "John Doe"; // AssertJ provides fluent assertions Assertions.assertThat(name) .isNotNull() .isNotEmpty() .startsWith("John") .endsWith("Doe") .hasSize(8) .contains(" ") .doesNotContain("Jane"); } @Test void assertJCollectionAssertions() { List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry"); Assertions.assertThat(fruits) .isNotEmpty() .hasSize(3) .contains("Banana") .doesNotContain("Orange") .containsExactly("Apple", "Banana", "Cherry") .containsSequence("Banana", "Cherry") .allMatch(fruit -> fruit.length() >= 5) .noneMatch(fruit -> fruit.isEmpty()); } @Test void assertJObjectAssertions() { Person person = new Person("John", 30); Assertions.assertThat(person) .isNotNull() .extracting(Person::getName, Person::getAge) .containsExactly("John", 30); Assertions.assertThat(person.getName()) .isEqualTo("John") .isNotEqualTo("Jane"); } @Test void assertJExceptionAssertions() { Calculator calculator = new Calculator(); Assertions.assertThatThrownBy(() -> calculator.divide(10, 0)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Cannot divide by zero") .hasNoCause(); Assertions.assertThatCode(() -> calculator.divide(10, 2)) .doesNotThrowAnyException(); } }

8. Advanced Assertion Patterns

Custom Assertion Methods

class CustomAssertionsTest { // Custom assertion method static void assertValidEmail(String email) { assertNotNull(email, "Email should not be null"); assertTrue(email.contains("@"), "Email should contain '@'"); assertTrue(email.contains("."), "Email should contain '.'"); assertTrue(email.length() >= 5, "Email should be at least 5 characters long"); } static void assertPositiveNumber(int number) { assertTrue(number > 0, () -> "Number should be positive, but was: " + number); } static void assertValidAge(int age) { assertAll("Age validation", () -> assertTrue(age >= 0, "Age cannot be negative"), () -> assertTrue(age <= 150, "Age cannot exceed 150") ); } @Test void usingCustomAssertions() { assertValidEmail("[email protected]"); assertPositiveNumber(42); assertValidAge(25); } } // Custom assertion class class UserAssertions { private final User actual; private UserAssertions(User actual) { this.actual = actual; } public static UserAssertions assertThat(User actual) { return new UserAssertions(actual); } public UserAssertions hasUsername(String expectedUsername) { assertEquals(expectedUsername, actual.getUsername(), "Username mismatch"); return this; } public UserAssertions hasEmailContaining(String domain) { assertTrue(actual.getEmail().contains(domain), "Email should contain domain: " + domain); return this; } public UserAssertions isActive() { assertTrue(actual.isActive(), "User should be active"); return this; } public UserAssertions hasLoginCount(int expectedCount) { assertEquals(expectedCount, actual.getLoginCount(), "Login count mismatch"); return this; } } class CustomAssertionClassTest { @Test void usingCustomAssertionClass() { User user = new User("john", "[email protected]", true, 5); UserAssertions.assertThat(user) .hasUsername("john") .hasEmailContaining("company") .isActive() .hasLoginCount(5); } }

9. Parameterized Test Assertions

Assertions in Parameterized Tests

import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.*; class ParameterizedAssertionsTest { @ParameterizedTest @ValueSource(ints = {1, 2, 3, 4, 5}) void testPositiveNumbers(int number) { assertTrue(number > 0); assertTrue(number < 10); } @ParameterizedTest @CsvSource({ "2, 3, 5", "0, 5, 5", "-1, 1, 0", "10, -5, 5" }) void testAddition(int a, int b, int expected) { int result = a + b; assertEquals(expected, result, () -> a + " + " + b + " should equal " + expected); } @ParameterizedTest @MethodSource("stringProvider") void testStringLength(String input, int expectedLength) { assertAll( () -> assertNotNull(input), () -> assertEquals(expectedLength, input.length()) ); } private static Stream<Arguments> stringProvider() { return Stream.of( Arguments.of("Hello", 5), Arguments.of("JUnit", 5), Arguments.of("Testing", 7) ); } @ParameterizedTest @NullAndEmptySource @ValueSource(strings = {" ", "\t", "\n"}) void testBlankStrings(String input) { assertTrue(input == null || input.trim().isEmpty()); } }

Best Practices Summary

  1. Use Descriptive Messages: Provide meaningful failure messages
  2. Prefer assertAll: Group related assertions to see all failures
  3. Use Supplier for Expensive Messages: Use () -> "message" for expensive message construction
  4. Test Exceptions Properly: Use assertThrows and verify exception properties
  5. Leverage Timeouts: Use timeout assertions for performance testing
  6. Consider AssertJ: Use AssertJ for more fluent and readable assertions
  7. Create Custom Assertions: Build domain-specific assertion methods
  8. Use Parameterized Tests: Reduce duplication with parameterized tests

This comprehensive guide covers all major assertion types in JUnit 5, from basic value comparisons to advanced patterns with custom assertions and third-party integrations.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper