Errors from dependencies: thenThrow and doThrow
The most valuable tests often check what happens when something goes wrong: the database is down, the mail server does not respond. With real dependencies such situations are hard to reproduce. A mock can throw any exception on command — and the test checks how the service reacts.
thenThrow
// src/test/java/com/example/blog/PostServiceErrorsTest.java
@Test
void databaseErrorReachesCaller() {
when(repository.save(any(Post.class)))
.thenThrow(new IllegalStateException("Database is down"));
IllegalStateException e = assertThrows(IllegalStateException.class,
() -> service.create("Hello", "anna"));
assertEquals("Database is down", e.getMessage());
}The repository mock throws on save(), and the test checks that create() does not swallow the error but passes it to the caller. As before, repository, notifier and service are fields of the test class.
doThrow for void methods
notifier.send() returns void, and when(notifier.send(...)) does not compile — you cannot pass void to a method. For such methods the order is reversed: first the action, then the mock and the call:
// src/test/java/com/example/blog/PostServiceErrorsTest.java
@Test
void mailErrorAfterSave() {
Post draft = new Post(1L, "Hello", "anna", false);
when(repository.findById(1L)).thenReturn(Optional.of(draft));
doThrow(new IllegalStateException("Mail server is not responding"))
.when(notifier).send(anyString(), anyString());
assertThrows(IllegalStateException.class, () -> service.publish(1L));
verify(repository).save(draft.publish());
}The test shows a real property of publish(): the post is saved first and the email is sent afterwards. If the mail fails, the post is already published, and the user gets an error. Whether this is right is a question for the developer — the test made the behavior visible.
The same family includes doReturn, doNothing and doAnswer; they are needed for void methods and for spies (next lessons).
Consecutive calls
Several thenReturn / thenThrow in a chain set answers for the first, second and next calls:
// src/test/java/com/example/blog/PostServiceErrorsTest.java
@Test
void consecutiveCalls() {
when(repository.findById(1L))
.thenReturn(Optional.of(new Post(1L, "Hello", "anna", false)))
.thenReturn(Optional.empty());
assertEquals("Hello", service.find(1L).title());
assertThrows(PostNotFoundException.class, () -> service.find(1L));
}The first call finds the post, the second does not — as if someone deleted it in between. The last answer repeats for all further calls.
Checked exceptions
A mock can throw only exceptions the method can actually throw. findById does not declare IOException, so this is refused:
when(repository.findById(anyLong())).thenThrow(new IOException("Disk is full"));org.mockito.exceptions.base.MockitoException:
Checked exception is invalid for this method!
Invalid: java.io.IOException: Disk is fullUnchecked exceptions (RuntimeException and its subclasses) are always allowed.
Common mistake
Writing when for a void method:
when(notifier.send(anyString(), anyString()))
.thenThrow(new IllegalStateException("Mail server is not responding"));The test does not even compile:
[ERROR] /.../src/test/java/com/example/blog/VoidWhenTest.java:[14,27] 'void' type not allowed hereFor void methods use doThrow(...).when(notifier).send(...). Note the brackets: when(notifier) gets the mock, and the method is called after it.