Java tutorials > Testing and Debugging > Testing > How to test exceptions?
How to test exceptions?
Testing for exceptions in Java is a crucial aspect of ensuring your code's robustness and reliability. It involves verifying that your code throws the expected exceptions under specific conditions. This tutorial explores different techniques for testing exceptions using JUnit, a popular testing framework.
Introduction to Exception Testing
Exception testing is about confirming that a piece of code behaves as expected when things go wrong. Instead of just testing the happy path, you also need to ensure that the code throws the right exception when, for example, it receives invalid input, can't connect to a database, or encounters a file that doesn't exist. In JUnit, there are a few ways to assert that exceptions are thrown. We'll cover the most common and recommended approaches.
Using assertThrows()
(JUnit 5)
JUnit 5's The test passes if the code inside the lambda throws an exception of the specified type (or a subclass thereof). If the code doesn't throw any exception, or throws an exception of a different type, the test fails. Explanation:assertThrows()
method provides a clean and readable way to test for exceptions. It takes two arguments: the expected exception class and a Executable
(a lambda expression or method reference that executes the code you want to test).
MyClass
.assertThrows(IllegalArgumentException.class, () -> myObject.riskyMethod(-1))
asserts that calling myObject.riskyMethod(-1)
throws an IllegalArgumentException
.
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertThrows;
class MyClass {
public void riskyMethod(int input) {
if (input < 0) {
throw new IllegalArgumentException("Input cannot be negative");
}
// ... other code ...
}
}
public class MyClassTest {
@Test
void testRiskyMethodThrowsException() {
MyClass myObject = new MyClass();
assertThrows(IllegalArgumentException.class, () -> myObject.riskyMethod(-1));
}
}
Concepts behind the Snippet
The core concept revolves around exception handling and assertion. We are essentially asserting that a particular condition (negative input) results in a specific exception (IllegalArgumentException). The assertThrows
method encapsulates the try-catch block typically used for exception handling and assertion, making the test code cleaner and more readable.
Real-Life Use Case Section
Consider a banking application. A method to withdraw money from an account should throw an InsufficientFundsException
if the user tries to withdraw more money than they have. Exception testing would verify that this exception is thrown under those conditions, preventing incorrect transactions.
Best Practices
Exception
unless absolutely necessary. This makes your tests more precise and easier to understand.assertThrows
to capture the exception and assert on its message.
Interview Tip
When discussing exception testing, be prepared to explain the importance of testing exceptional scenarios, the different methods available in JUnit for testing exceptions (assertThrows
, rule-based testing (deprecated, but good to know), try-catch block), and the benefits of using specific exception types in assertions.
When to Use Them
Use exception testing whenever a method is designed to throw an exception under certain circumstances. This is especially important for methods that handle user input, interact with external resources (databases, files, APIs), or perform complex calculations where errors are possible.
Memory Footprint
The memory footprint of exception testing is typically negligible. The main overhead comes from creating the objects needed for the test and the temporary creation of the exception object when it's thrown. This overhead is generally insignificant compared to the benefits of thorough testing.
Alternatives
Rule-based Exception Testing (JUnit 4): JUnit 4 used Try-Catch Blocks: You can manually use try-catch blocks to test for exceptions, but this approach is more verbose and less elegant than using ExpectedException
rule. While still functional, it's generally considered less readable and maintainable than assertThrows
. The code snippet shows how you can use rule-based exception testing.assertThrows
or ExpectedException
. It involves wrapping the code that's expected to throw an exception in a try block and catching the expected exception in the catch block. Inside the catch block, you then assert that the caught exception is of the correct type. It is not recommended for new code.
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
public class MyClassTest {
@Rule
public ExpectedException exceptionRule = ExpectedException.none();
@Test
public void testRiskyMethodThrowsException() {
MyClass myObject = new MyClass();
exceptionRule.expect(IllegalArgumentException.class);
exceptionRule.expectMessage("Input cannot be negative");
myObject.riskyMethod(-1);
}
}
Pros and Cons of assertThrows()
Pros: Cons:
FAQ
-
How can I assert the message of the exception?
With
assertThrows()
, you capture the thrown exception and then assert on its message:Exception exception = assertThrows(IllegalArgumentException.class, () -> myObject.riskyMethod(-1)); assertEquals("Input cannot be negative", exception.getMessage());
-
What happens if the method doesn't throw an exception?
assertThrows()
will fail the test if the code you provide in the lambda expression doesn't throw any exception or if it throws an exception of a different type than the one you specified. -
Can I test if multiple exceptions are thrown?
No,
assertThrows()
is designed to test for a single, specific exception. If you need to test for multiple exceptions (which is generally not recommended), you would typically write separate tests for each exception condition.