Mockito Tutorial: Mocking Dependencies in Java Tests
Learn how to use Mockito to write fast, focused unit tests. Understand mocks, stubs, spies, argument captors, and how to avoid over-mocking.
What you'll learn
- ✓When to use mocks instead of real collaborators
- ✓Stubbing return values and exceptions
- ✓Verifying interactions with mocks
- ✓Argument captors and spies
- ✓Avoiding the over-mocking trap
Prerequisites
- •Basic Java
- •JUnit 5 basics
What and Why
Mockito is a Java library for creating test doubles. When the class under test depends on something slow, unreliable, or hard to set up (a database, a payment gateway, a clock), Mockito lets you substitute a stand-in object whose behavior you control.
The reason to mock is isolation. A failing test should point at one defect, not a chain of broken services. By replacing collaborators with mocks, you ensure that a red test means the unit under test is wrong, not the network.
Mental Model
A mock has two roles. It is a stub, meaning you can program it to return specific values for specific calls. It is also a spy on interactions, meaning you can verify afterward that the unit under test called the right methods with the right arguments.
Think of a mock as a hollow puppet. You wire its strings before the test (when(...).thenReturn(...)), run your code, then inspect what the puppet did (verify(...)). The real object is never created.
Hands-on Example
Imagine an OrderService that depends on a PaymentGateway and a Notifier.
public class OrderService {
private final PaymentGateway gateway;
private final Notifier notifier;
public OrderService(PaymentGateway g, Notifier n) {
this.gateway = g; this.notifier = n;
}
public boolean placeOrder(Order o) {
if (!gateway.charge(o.getAmount())) return false;
notifier.sendConfirmation(o.getEmail());
return true;
}
}
A Mockito test:
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock PaymentGateway gateway;
@Mock Notifier notifier;
@InjectMocks OrderService service;
@Test
void sendsConfirmationOnSuccessfulCharge() {
when(gateway.charge(100)).thenReturn(true);
Order o = new Order(100, "a@b.com");
boolean result = service.placeOrder(o);
assertTrue(result);
verify(notifier).sendConfirmation("a@b.com");
}
@Test
void skipsNotificationOnFailedCharge() {
when(gateway.charge(anyInt())).thenReturn(false);
service.placeOrder(new Order(50, "x@y.com"));
verify(notifier, never()).sendConfirmation(anyString());
}
}
+----------------+
| Test Method |
+-------+--------+
|
v
+----------------+ +-----------------+
| OrderService |----->| PaymentGateway | (mock)
| (real) | +-----------------+
| |
| |----->+-----------------+
| | | Notifier | (mock)
+----------------+ +-----------------+
^ ^
| |
verify() and when().thenReturn()
assertions configures responses The unit under test is real. Only its collaborators are fake. This is the boundary you should keep in mind.
Common Pitfalls
Mocking the class under test is the classic mistake. If you mock OrderService, you are testing your stubs, not your code. Mock only collaborators.
Mocking value objects like String, List, or your own data classes is almost always wrong. Use real instances; they are cheap and behave correctly.
Over-verification creates brittle tests. If you verify every interaction, refactoring a method to make an extra call breaks ten tests. Verify behavior the caller cares about; let implementation details be implementation details.
when(...).thenReturn(...) on a void method throws a confusing error. Use doNothing().when(mock).method() or doThrow(...).when(mock).method() instead.
Forgetting @ExtendWith(MockitoExtension.class) (or MockitoAnnotations.openMocks(this)) leaves @Mock fields as null and produces NullPointerException.
Practical Tips
Use ArgumentCaptor when you need to assert on complex arguments:
ArgumentCaptor<Email> captor = ArgumentCaptor.forClass(Email.class);
verify(notifier).send(captor.capture());
assertEquals("a@b.com", captor.getValue().getTo());
Prefer @InjectMocks to wire dependencies, but only when constructors are unambiguous. For complex setups, build the object by hand in the test.
Use spies sparingly. A spy is a partial mock around a real object. They are useful when refactoring legacy code, but a class that needs spying often signals a design problem.
Keep stubs minimal. Stub the calls your test cares about; let other methods return Mockito’s defaults (null, 0, false). Over-stubbing makes intent hard to read.
Wrap-up
Mockito is small, focused, and powerful. Master five primitives, mock, when, verify, ArgumentCaptor, and @InjectMocks, and you can test almost any Java class in isolation.
The hardest skill is not learning the API but knowing when not to mock. When tests get hard to write, that is feedback about the design under test. Listen to it.
Related articles
- Java JUnit 5 Tutorial: Writing Clean, Modern Java Tests
A complete introduction to JUnit 5. Learn the new architecture, lifecycle, parameterized tests, assertions, and patterns that keep your test suite fast and readable.
- Java Java Collections Framework Cheatsheet
A pragmatic tour of Java's collection interfaces and implementations, with guidance on choosing between List, Set, Map, and Queue variants in real applications.
- Java Java Concurrency with CompletableFuture
How CompletableFuture composes asynchronous work in Java: chaining, combining, error handling, executors, and the patterns that keep concurrent code readable.
- Java Java Exception Handling Best Practices
Clear rules for using checked and unchecked exceptions in Java, with patterns for wrapping, logging, and propagating errors in real production code.