0
0
JUnittesting~15 mins

when().thenThrow() for exceptions in JUnit - Deep Dive

Choose your learning style9 modes available
Overview - when().thenThrow() for exceptions
What is it?
when().thenThrow() is a way to tell a test double (mock) to throw an exception when a specific method is called. It helps simulate error conditions in code so you can check how your program handles problems. This is useful in unit testing to make sure your code reacts correctly to unexpected failures.
Why it matters
Without when().thenThrow(), you cannot easily test how your code behaves when things go wrong, like network failures or invalid inputs. This means bugs related to error handling might go unnoticed until they cause real problems. Using this method helps catch those bugs early, making software more reliable and safer for users.
Where it fits
Before learning this, you should understand basic unit testing and mocking concepts in JUnit and Mockito. After mastering when().thenThrow(), you can explore advanced exception testing, verifying exception messages, and combining it with other Mockito features like doThrow() or spying.
Mental Model
Core Idea
when().thenThrow() tells a mock to act like it failed by throwing an exception when a method is called, so you can test your code's error handling.
Think of it like...
It's like setting a fake vending machine to jam and spit out an error when you press a button, so you can see how you react to the machine breaking.
┌───────────────┐       call method       ┌───────────────┐
│   Test Code   │ ──────────────────────▶ │   Mock Object │
└───────────────┘                        │
                                         │
                                         │ throws Exception
                                         ▼
                                  ┌───────────────┐
                                  │ Exception Thrown│
                                  └───────────────┘
Build-Up - 6 Steps
1
FoundationBasics of Mocking in JUnit
🤔
Concept: Learn what mocking means and how it helps isolate code for testing.
Mocking means creating a fake version of a class or interface that behaves in a controlled way. In JUnit tests, Mockito is a popular library to create mocks. For example, you can mock a service that returns fixed data instead of calling a real database.
Result
You get a controlled test environment where you can predict and verify interactions without relying on real dependencies.
Understanding mocking is essential because it lets you test parts of your code independently, making tests faster and more reliable.
2
FoundationIntroduction to Exception Handling in Tests
🤔
Concept: Understand why testing exception scenarios is important.
Your code should handle errors gracefully. Tests must check that when something goes wrong, your code reacts properly. For example, if a method throws an exception, your code might catch it and log a message or retry.
Result
You realize that testing only the 'happy path' is not enough; error paths need tests too.
Knowing that exceptions can happen anytime helps you write tests that make your software robust and user-friendly.
3
IntermediateUsing when().thenThrow() to Simulate Exceptions
🤔Before reading on: do you think when().thenThrow() can throw any exception type or only runtime exceptions? Commit to your answer.
Concept: Learn how to configure a mock to throw exceptions when a method is called.
In Mockito, you write: when(mock.method()).thenThrow(new ExceptionType()). This tells the mock to throw the specified exception whenever that method is called during the test. You can throw checked or unchecked exceptions.
Result
Your test can now simulate error conditions and verify how your code handles them.
Knowing that you can simulate exceptions on mocks lets you test error handling without causing real failures.
4
IntermediateVerifying Exception Handling in Your Code
🤔Before reading on: do you think catching exceptions in tests is enough, or should you also verify mock interactions? Commit to your answer.
Concept: Check that your code responds correctly to exceptions thrown by mocks.
Write tests that expect exceptions using JUnit's assertThrows or try-catch blocks. Also verify that after an exception, your code calls fallback methods or logs errors. For example: @Test void testExceptionHandling() { when(mock.method()).thenThrow(new IOException()); assertThrows(IOException.class, () -> testedObject.callMethod()); verify(mock).method(); }
Result
You confirm that your code both detects and reacts properly to exceptions.
Testing exception handling fully means checking both the thrown exception and the resulting behavior, ensuring your code is resilient.
5
AdvancedChaining thenThrow() for Multiple Calls
🤔Before reading on: do you think thenThrow() can throw different exceptions on consecutive calls? Commit to your answer.
Concept: Configure mocks to throw different exceptions on multiple calls to the same method.
You can chain thenThrow() calls like this: when(mock.method()) .thenThrow(new IOException()) .thenThrow(new RuntimeException()); This means the first call throws IOException, the second throws RuntimeException.
Result
Your tests can simulate complex failure scenarios with changing errors over time.
Understanding chained exceptions helps test retry logic and error recovery in real-world systems.
6
ExpertLimitations and Alternatives to when().thenThrow()
🤔Before reading on: do you think when().thenThrow() works with void methods? Commit to your answer.
Concept: Explore when().thenThrow() limitations and alternative Mockito methods.
when().thenThrow() does NOT work with void methods because they don't return values. Instead, use doThrow(). For example: doThrow(new Exception()).when(mock).voidMethod(); Also, be aware that throwing checked exceptions requires declaring them or wrapping in RuntimeException.
Result
You avoid common mistakes and choose the right method for exception simulation.
Knowing the boundaries of when().thenThrow() prevents test failures and improves test design.
Under the Hood
Mockito creates proxy objects that intercept method calls on mocks. When you use when().thenThrow(), Mockito stores the instruction to throw a specific exception when that method is called. At runtime, when the method is invoked, the proxy checks its instructions and throws the exception instead of executing real code.
Why designed this way?
This design allows tests to isolate the unit under test by replacing dependencies with controllable mocks. Throwing exceptions on demand simulates error conditions without needing real failures, making tests fast and deterministic.
┌───────────────┐
│ Test Code     │
└──────┬────────┘
       │ calls method
       ▼
┌───────────────┐
│ Mockito Proxy │
│ (Mock Object) │
└──────┬────────┘
       │ checks instructions
       ▼
┌─────────────────────────────┐
│ Instruction: thenThrow(Exception) │
└─────────────────────────────┘
       │ throws exception
       ▼
┌───────────────┐
│ Exception     │
└───────────────┘
Myth Busters - 3 Common Misconceptions
Quick: Does when().thenThrow() work with void methods? Commit to yes or no.
Common Belief:when().thenThrow() can be used with any method, including void methods.
Tap to reveal reality
Reality:when().thenThrow() does NOT work with void methods; you must use doThrow() for those.
Why it matters:Using when().thenThrow() on void methods causes runtime errors and broken tests, wasting time debugging.
Quick: Can thenThrow() throw checked exceptions without declaring them? Commit to yes or no.
Common Belief:You can throw any checked exception with thenThrow() without declaring it in the test method signature.
Tap to reveal reality
Reality:Checked exceptions must be declared or handled; otherwise, the test code won't compile.
Why it matters:Ignoring this leads to compilation errors and confusion about exception handling in tests.
Quick: Does thenThrow() affect real objects or only mocks? Commit to your answer.
Common Belief:thenThrow() can be used on real objects to force exceptions.
Tap to reveal reality
Reality:thenThrow() only works on mocks created by Mockito, not on real instances.
Why it matters:Trying to use thenThrow() on real objects fails silently or causes errors, leading to false test results.
Expert Zone
1
thenThrow() can chain multiple exceptions for consecutive calls, enabling simulation of complex failure sequences.
2
Using thenThrow() with checked exceptions requires careful test method signature management to avoid compilation issues.
3
Mockito's internal proxy mechanism means that thenThrow() instructions are stored and matched by method signature, so overloaded methods need precise mocking.
When NOT to use
Do not use when().thenThrow() for void methods; use doThrow() instead. Also, avoid using thenThrow() on spies or partial mocks where real method behavior is needed. For asynchronous code, consider other testing strategies like CompletableFuture exception handling.
Production Patterns
In real-world tests, when().thenThrow() is used to simulate network failures, database errors, or invalid inputs. It helps verify retry logic, fallback mechanisms, and error logging. Combined with assertThrows, it ensures robust exception handling in critical code paths.
Connections
Exception Handling
builds-on
Understanding when().thenThrow() deepens knowledge of how exceptions propagate and are managed in software.
Mocking and Stubbing
same pattern
when().thenThrow() is a specialized form of stubbing that focuses on error simulation, enriching the general mocking toolkit.
Fault Injection in Systems Engineering
similar pattern
Both simulate failures to test system resilience; knowing fault injection helps appreciate the value of when().thenThrow() in software testing.
Common Pitfalls
#1Using when().thenThrow() on a void method causes errors.
Wrong approach:when(mock.voidMethod()).thenThrow(new RuntimeException());
Correct approach:doThrow(new RuntimeException()).when(mock).voidMethod();
Root cause:Misunderstanding that when() requires a return value, which void methods lack.
#2Throwing checked exceptions without declaring them in test method signature.
Wrong approach:@Test void test() { when(mock.method()).thenThrow(new IOException()); testedObject.callMethod(); }
Correct approach:@Test void test() throws IOException { when(mock.method()).thenThrow(new IOException()); testedObject.callMethod(); }
Root cause:Ignoring Java's checked exception rules in test code.
#3Trying to use thenThrow() on real objects instead of mocks.
Wrong approach:when(realObject.method()).thenThrow(new RuntimeException());
Correct approach:MyClass mock = mock(MyClass.class); when(mock.method()).thenThrow(new RuntimeException());
Root cause:Confusing mocks with real instances; thenThrow() only works on mocks.
Key Takeaways
when().thenThrow() is a Mockito feature to simulate exceptions from mocked methods, enabling testing of error handling.
It works only on mocks and requires special handling for void methods using doThrow().
Testing exception scenarios ensures your code behaves correctly under failure conditions, improving software reliability.
Chaining thenThrow() calls allows simulation of multiple failures in sequence, useful for testing retries.
Understanding the limitations and correct usage of when().thenThrow() prevents common test errors and improves test quality.