Labs ICT
โญ Pro Login

Testing Exceptions

One of the most common things you need to test is whether your code throws the right exceptions under the right conditions. JUnit 5 makes this straightforward with assertThrows. Let us see how to test exceptions properly.

Basic Exception Testing


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

class ExceptionTest {

    @Test
    void shouldThrowException() {
        assertThrows(IllegalArgumentException.class, () -> {
            throw new IllegalArgumentException("Invalid input");
        });
    }

    @Test
    void calculatorShouldThrowOnDivideByZero() {
        Calculator calc = new Calculator();
        assertThrows(ArithmeticException.class,
            () -> calc.divide(10, 0)
        );
    }
}
    

Checking the Exception Message

Often you want to verify not just the exception type, but also the message:


@Test
void shouldThrowWithSpecificMessage() {
    IllegalArgumentException exception = assertThrows(
        IllegalArgumentException.class,
        () -> validateAge(-5)
    );
    assertEquals("Age must be positive", exception.getMessage());
}

@Test
void shouldThrowWithMessageContaining() {
    IllegalArgumentException exception = assertThrows(
        IllegalArgumentException.class,
        () -> validateEmail("not-an-email")
    );
    assertTrue(exception.getMessage().contains("invalid email"));
}
    

Testing Multiple Exceptions


@Test
void shouldThrowDifferentExceptions() {
    // Test case 1: null input
    assertThrows(NullPointerException.class,
        () -> processString(null)
    );

    // Test case 2: empty input
    assertThrows(IllegalArgumentException.class,
        () -> processString("")
    );

    // Test case 3: valid input (no exception)
    assertDoesNotThrow(() -> processString("hello"));
}

@Test
void shouldNotThrowException() {
    // assertDoesNotThrow verifies NO exception is thrown
    assertDoesNotThrow(() -> {
        int result = 2 + 2;
        assertEquals(4, result);
    });
}
    

Exception Assertion with Executable

You can capture the exception and do more complex assertions:


@Test
void shouldThrowWithCause() {
    Exception exception = assertThrows(RuntimeException.class, () -> {
        try {
            throw new IOException("File not found");
        } catch (IOException e) {
            throw new RuntimeException("Failed to process", e);
        }
    });

    assertEquals("Failed to process", exception.getMessage());
    assertTrue(exception.getCause() instanceof IOException);
    assertEquals("File not found", exception.getCause().getMessage());
}
    

JUnit 4 vs JUnit 5 Exception Testing


// JUnit 4 approach
@Test(expected = ArithmeticException.class)
public void divideByZero() {
    calc.divide(10, 0);
}

// JUnit 5 approach (more powerful)
@Test
void divideByZero() {
    ArithmeticException exception = assertThrows(
        ArithmeticException.class,
        () -> calc.divide(10, 0)
    );
    assertEquals("Cannot divide by zero", exception.getMessage());
}
    

The JUnit 5 approach is better because you can inspect the exception object, check the message, verify the cause, and do other assertions. JUnit 4 only let you check the exception type.

Common Patterns


class InputValidatorTest {

    @Test
    void rejectsNull() {
        assertThrows(NullPointerException.class,
            () -> validator.validate(null));
    }

    @Test
    void rejectsEmptyString() {
        IllegalArgumentException ex = assertThrows(
            IllegalArgumentException.class,
            () -> validator.validate("")
        );
        assertEquals("Input cannot be empty", ex.getMessage());
    }

    @Test
    void rejectsTooLong() {
        String longString = "a".repeat(1001);
        assertThrows(IllegalArgumentException.class,
            () -> validator.validate(longString));
    }

    @Test
    void acceptsValidInput() {
        assertDoesNotThrow(() -> validator.validate("hello"));
    }
}
    

AssertThrows Returns the Exception

A key detail: assertThrows returns the exception that was thrown. This lets you chain assertions:


@Test
void detailedExceptionCheck() {
    ValidationException ex = assertThrows(
        ValidationException.class,
        () -> service.process(new Request("", -1))
    );

    assertEquals(2, ex.getErrors().size());
    assertEquals("name", ex.getErrors().get(0).getField());
    assertEquals("amount", ex.getErrors().get(1).getField());
}
    

๐Ÿงช Quick Quiz

Which class is used to test exceptions in JUnit 5?