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
- Use Descriptive Messages: Provide meaningful failure messages
- Prefer assertAll: Group related assertions to see all failures
- Use Supplier for Expensive Messages: Use
() -> "message"for expensive message construction - Test Exceptions Properly: Use
assertThrowsand verify exception properties - Leverage Timeouts: Use timeout assertions for performance testing
- Consider AssertJ: Use AssertJ for more fluent and readable assertions
- Create Custom Assertions: Build domain-specific assertion methods
- 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.