Mockito: Mocking, Stubbing, Verifications, Spy vs Mock
Learn Mockito fundamentals: create mocks and spies, stub behavior with when().thenReturn(), verify interactions, and choose between Spy vs Mock wisely.
Learn Mockito fundamentals: create mocks and spies, stub behavior with when().thenReturn(), verify interactions, and choose between Spy vs Mock wisely. The guide uses practical examples to explain what is mockito and why it matters in spring boot, when to use mockito and when to choose something else and shows how to apply the ideas in a Spring Boot project.
Mockito: Mocking, Stubbing, Verifications, Spy vs Mock
Mockito is the most widely used mocking framework for Java unit tests. It substitutes fake implementations for real collaborators so you can test one class in isolation, keep assertions deterministic, and verify that objects actually send messages to each other. In Spring Boot, Mockito is what makes @WebMvcTest and @DataJpaTest slices actually useful.
This guide walks through everything you need to start writing meaningful Mockito-based tests: the mental model behind mock objects, the essential API calls you will reach for daily, the trade-offs between spies and mocks, and the pitfalls that trip up even experienced developers.
What is Mockito and Why It Matters in Spring Boot
Spring Boot encourages testing in slices. When you write a @WebMvcTest for a controller, the web layer loads but the service layer does not start. The problem is that your controller depends on a service bean, and Spring cannot find it. Mockito lets you substitute a mock where the real service bean would normally live.
@WebMvcTest(MyController.class)
class MyControllerTest {
@MockBean
private MyService myService;
@Autowired
private MockMvc mockMvc;
@Test
void getCustomer_returnsCustomerFromService() throws Exception {
when(myService.findById(1L)).thenReturn(new Customer("Alice", "alice@example.com"));
mockMvc.perform(get("/customers/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("Alice"));
}
}
The @MockBean annotation does two things. First, it creates a Mockito mock for MyService. Second, it replaces any existing bean in the Spring context with your mock. The test runs fast because Spring starts only the web slice, but your controller still receives a fully wired collaborator.
When to Use Mockito and When to Choose Something Else
Mockito excels at unit testing isolated classes with external dependencies, particularly controller tests and service tests where you need fine-grained control over what collaborators return.
That said, Mockito is not always the right choice. For integration tests that need a real database, use Testcontainers or an embedded database. For microservice contract testing, look at Pact or Spring Cloud Contract. For tests that need to replay actual HTTP responses, WireMock is more expressive for matching headers, query parameters, and request bodies.
EasyMock came before Mockito and uses a similar test double concept, but its record-run-verify syntax is more cumbersome. JMockit relies on bytecode manipulation with different runtime behavior. Most Spring Boot projects settle on Mockito for its gentler learning curve, IDE autocomplete support, and @MockBean integration.
How Mocks and Spies Flow Through a Test
The diagram below shows the lifecycle of a mock or spy in a typical test. You create the double, configure its behavior, inject it into the subject under test, exercise the subject, and finally verify that the right calls happened.
graph TD
A["Test Starts"] --> B["Create Mock or Spy"]
B --> C["Stub Behavior<br/>when thenReturn"]
C --> D["Inject into<br/>System Under Test"]
D --> E["Exercise Subject<br/>Call Methods"]
E --> F["Verify Interactions<br/>verify method"]
F --> G["Test Pass or Fail"]
The flow is the same for both mocks and spies, but the semantics differ. A mock starts with all methods returning safe defaults: null, empty collections, zero primitives. A spy wraps a real instance, so its methods execute unless you stub them otherwise.
Failure Scenarios You Will Encounter
Unstubbed Methods Returning Null
Mockito returns null for object return types by default. This means calling an unstubbed method on a mock can produce a NullPointerException if you pass the result directly into code that expects a non-null value.
@Test
void sendingNotification_whenUserIsNull() {
when(userService.findById(1L)).thenReturn(null);
// This throws NullPointerException because getEmail() is called on null
assertThrows(NullPointerException.class, () -> {
notificationService.notifyUser(1L, "Hello");
});
}
The fix is straightforward: stub every method whose return value your code dereferences.
Strict Stubbing Violations
Mockito Strict Stubbing mode (@MockitoSettings(strictness = Strictness.STRICT_STUBS)) fails a test if you invoke a method that was not explicitly stubbed. This catches tests that do not fully declare their expectations and prevents accidental use of unstubbed behavior.
@MockitoSettings(strictness = Strictness.STRICT_STUBS)
class StrictStubbingTest {
@Mock
private OrderRepository orderRepository;
@Test
void placeOrder_shouldConfirmOrder() {
// forgot to stub getOrderCount()
when(orderRepository.getOrderCount()).thenReturn(5);
// calling save() was never stubbed — strict stubbing catches this
orderService.placeOrder(new Order());
}
}
With strict stubbing enabled, Mockito fails on any unstubbed method call. This catches tests that lean on accidental behavior rather than declared expectations — especially useful in large suites where tight coupling between tests can obscure real bugs.
Trade-Off Table: Spy vs Mock vs Real Objects
| Aspect | Mock | Spy | Real Object |
|---|---|---|---|
| Default behavior | Returns null/empty defaults | Delegates to real object | Executes real logic |
| Stubbing | All methods must be stubbed | Only stub what you need | Not applicable |
| Verification | Full interaction tracking | Full interaction tracking | Manual assertions |
| Speed | Fastest | Fast | Depends on real dependency |
| Isolation | Complete | Partial (real object still exists) | None |
| Use when | Collaborator is complex or has side effects | You need real behavior for most calls | Real object is simple and deterministic |
| Risk | Tests can become coupled to implementation | Spy may call real external systems | Tests become non-deterministic |
Prefer mocks for external services, repositories, and any class with I/O. Use spies when you want to test a single method on a real object while still verifying that other methods on the same instance were called. Avoid real objects in unit tests unless the object is a simple value type with no dependencies.
Essential Mockito API in Practice
Setting Up Mocks with @MockBean
In Spring Boot tests, @MockBean registers a mock in the application context and replaces any existing bean of the same type.
@SpringBootTest
class IntegrationTest {
@MockBean
private PaymentGateway paymentGateway;
@Autowired
private OrderService orderService;
@Test
void checkout_successfulPayment() {
when(paymentGateway.process(anyDouble())).thenReturn(true);
Order order = new Order(BigDecimal.valueOf(99.99));
orderService.checkout(order);
assertEquals(OrderStatus.PAID, order.getStatus());
}
}
Stubbing Behavior with when().thenReturn()
The when().thenReturn() chain sets up a stubbed return value for a method call.
// Simple return value
when(userRepository.findById(1L)).thenReturn(Optional.of(new User("Alice")));
// Throwing an exception
when(cache.get("key")).thenThrow(new RuntimeException("Cache unavailable"));
// Sequential returns (last one wins for subsequent calls)
when(counter.increment()).thenReturn(1).thenReturn(2).thenReturn(3);
// doReturn syntax (useful when spying)
doReturn("stubbed").when(spy).getData();
Verifying Interactions with verify()
Verification confirms that a method was called, how many times, and with which arguments.
@Test
void transferFunds_movesMoneyBetweenAccounts() {
Account source = new Account("A123", BigDecimal.valueOf(1000));
Account target = new Account("B456", BigDecimal.ZERO);
transferService.transfer(source, target, BigDecimal.valueOf(250));
verify(accountRepository, times(1)).save(source);
verify(accountRepository, times(1)).save(target);
verify(accountRepository, never()).delete(any());
}
Common verification modifiers:
times(n)— exactly n invocationsnever()— zero invocationsatLeastOnce()— one or more invocationsatMost(n)— at most n invocationstimeout(ms).times(n)— verify within a time window
Matching Arguments with ArgumentMatchers
Argument matchers let you write flexible stubs and verifications that match broad patterns instead of exact values.
// Stub with any() matcher
when(inventory.lookup(anyString())).thenReturn(new Item("SKU-001"));
// Stub with combined matchers
when(reportGenerator.generate(anyLong(), argThat(year -> year > 2020)))
.thenReturn("Report Generated");
// Verify with argument captor
ArgumentCaptor<String> skuCaptor = ArgumentCaptor.forClass(String.class);
verify(inventory).lookup(skuCaptor.capture());
assertTrue(skuCaptor.getValue().startsWith("SKU-"));
Common matchers:
any()— matches any argumentanyString(),anyLong(),anyInt(),anyDouble()— typed anyeq(value)— exact match with type safetyargThat(Predicate<T>)— custom predicate matchingisNull(),isNotNull()— null checkscontains(substring),startsWith(prefix)— string matching
Capturing Arguments with @Captor
ArgumentCaptor lets you grab actual argument values passed during verification for detailed assertions.
class NotificationServiceTest {
@Mock
private EmailSender emailSender;
@InjectMocks
private NotificationService notificationService;
@Captor
private ArgumentCaptor<EmailMessage> messageCaptor;
@Test
void sendWelcomeEmail_hasCorrectSubject() {
notificationService.sendWelcome("alice@example.com", "Alice");
verify(emailSender).send(messageCaptor.capture());
EmailMessage message = messageCaptor.getValue();
assertThat(message.getSubject()).isEqualTo("Welcome, Alice!");
assertThat(message.getRecipient()).isEqualTo("alice@example.com");
}
}
Observability Checklist for Mockito Tests
Use this checklist to make your Mockito tests maintainable and readable.
- Stub every method that returns a non-primitive type and is called by the subject under test
- Use descriptive stub setups that read like documentation:
when(service.getUser(id)).thenReturn(user) - Verify only the interactions that matter for the assertion, not every single call
- Keep stubs and verifications close to the test that uses them, not in setup methods
- Name mocks with the collaborator’s role, not its implementation:
userRepositorynotmockUserDao - Prefer
ArgumentMatchersover captors when simple matching suffices - Use
assertThrows()from JUnit 5 instead of try-catch blocks for exception testing - Check that your test fails for the right reason by temporarily breaking the subject under test
- Ensure strict stubbing is enabled in CI to catch unused stubs early
Security Notes for Test Isolation
Tests share state more than you might think. A common mistake is stubbing in a @BeforeEach method but forgetting that these stubs persist across test methods. If one test modifies the mock state, it can leak into subsequent tests.
@BeforeEach
void setup() {
when(repository.findActive()).thenReturn(List.of()); // shared across tests!
}
Isolate each test by resetting mocks explicitly when needed.
@AfterEach
void tearDown() {
reset(repository); // clean up after each test
}
Alternatively, use @MockitoSettings(strictness = Strictness.LENIENT) on individual tests that intentionally reuse stubs, or refactor shared setup into a helper method that each test calls explicitly.
Never commit test code that prints sensitive data or logs mock interactions with real credentials. Even in tests, treat user data with the same care you apply in production.
Common Pitfalls and How to Avoid Them
Method Call Order on Mocks
Mockito verifies method calls in the order they were made when you use InOrder.
@Test
void workflow_processesStepsInOrder() {
InOrder inOrder = inOrder(workflowEngine);
workflowEngine.start();
workflowEngine.process();
workflowEngine.complete();
inOrder.verify(workflowEngine).start();
inOrder.verify(workflowEngine).process();
inOrder.verify(workflowEngine).complete();
}
Without InOrder, Mockito verifies each call independently and does not care about sequence.
Final Classes and Methods
Mockito cannot mock final classes or methods by default. If you try, you get an Cannot mock/spy class exception.
Cannot mock/spy com.example.FinalPaymentService
You have two ways forward. One is to add the mockito-inline extension, which manipulates bytecode directly instead of subclassing.
// In your test or build configuration
@Configuration
class MockitoConfig {
@Bean
public MockitoMockFactoryBean mockitoMockFactoryBean() {
return new MockitoMockFactoryBean();
}
}
The better long-term move is to design classes to be mockable in the first place: program to interfaces, and avoid marking service classes as final if they need to be tested.
Static Methods
Mockito 3.4.0 and later can mock static methods with mockStatic().
@Test
void readingConfig_loadsFromStaticSource() {
try (var mockedConfig = mockStatic(Config.class)) {
mockedConfig.when(Config::getApiKey).thenReturn("test-key-123");
String key = Config.getApiKey();
assertEquals("test-key-123", key);
mockedConfig.verify(Config::getApiKey, times(1));
}
}
Try to keep static methods out of your domain code. They are hard to test in isolation and usually signal a design that would be better off with dependency injection.
When NOT to Use
Mockito works well for unit testing isolated classes, but it is not the right tool for every situation. Knowing when to skip Mockito keeps your test suite trustworthy and your tests actually useful.
When You Need Real Database Behavior
Mocks replace everything with stubs. A mocked database connection will never throw a SQLException from a real query timeout, and a mocked repository will never surface an N+1 problem in your JPQL. If you need to see how the database behaves under real load, use Testcontainers instead. Testcontainers spins up real Docker containers for PostgreSQL, MySQL, MongoDB, and more, so you get genuine database behavior in an isolated, disposable environment.
When Testing Integration with Real HTTP Services
Stubbing an HttpClient call with when(httpClient.get(anyString())).thenReturn(okResponse) does not tell you whether your code handles redirect loops, gzip compression, or 429 rate-limit responses correctly. For HTTP integration testing, use WireMock to set up a real HTTP server that can return specific status codes, headers, and response bodies, or point your test at a real staging environment.
When the Class Is Too Simple to Need Mocking
If a class is just a thin wrapper that delegates to one collaborator with no conditional logic, mocking adds noise without value. A test that calls new OrderService(new InMemoryOrderRepository()) and asserts on the result is easier to read than a mock-heavy alternative. Only reach for mocks when the class has branching behavior, multiple collaborators, or side effects you need to verify in isolation.
When Testing Final Classes Without Inline Extension Configured
Mockito cannot mock final classes by default. If you try mock(FinalClass.class) without the right configuration, you get an exception at runtime instead of a test failure that points to the real problem. You can enable final class mocking with the mockito-inline artifact and a mockito-extensions/org.mockito.plugins.MockMaker file containing mock-maker-inline, but this adds build configuration overhead. If you work with final classes often, consider whether the code under test is designed for testability, or whether you should pick a different testing strategy.
Production Failure Scenarios
Mockito tests that pass in isolation sometimes fail in production. Here are the most common ways that happens.
Unstubbed Methods Returning null Causing NPE
Mockito returns null for object return types by default. If your code calls a method on that returned object, you get a NullPointerException at runtime, not during test development. This often happens when a stubbed repository returns an entity and the code under test calls a method on a lazily-loaded associated object that was never stubbed. Production data triggers these code paths that your test data never touched.
Strict Stubbing Violations Explained
Strict stubbing mode (Mockito.inStrictStub() or @MockitoSettings(strictness = Strictness.STRICT_STUBS)) detects when you stub a method that is never called during the test. This catches typos in stub setups and ensures tests only define the mocks they actually use. Tests with unused stubs can mask bugs because the stub was written for a different scenario but never cleaned up. When strict stubbing is enforced, an unexpected stub invocation in production usually means your test coverage has a gap.
Final Class Mocking Issues
If your CI pipeline uses mockito-inline but a developer runs tests locally with a standard mockito-core JAR, tests pass in CI but fail locally (or vice versa). This is a build environment mismatch that often surfaces during onboarding when a new developer’s setup differs from CI configuration.
Static Method Mocking Problems
Static method mocking with mockStatic(…) is scoped to a try-with-resources block. If you forget to close the scope or accidentally let a mocked static leak into another test running in the same JVM, you get cross-test contamination. This shows up as flaky tests that pass individually but fail in suite execution. Parallel test execution makes this worse.
Incorrect Argument Matching
Using any() too broadly can mask real bugs. If you stub when(mockRepo.findById(any())).thenReturn(entity) and your code accidentally passes null, the mock still returns the entity instead of propagating the null check your production code should perform. The test passes, but a production call with a null argument returns unexpected data. Use precise matchers like eq(), argThat(), or custom matchers that validate argument state rather than just presence.
Quick Recap Checklist
Use this checklist before marking a Mockito test as complete.
- Mock or spy created for every collaborator the subject under test needs
- All non-primitive return values stubbed with
when().thenReturn() -
verify()called for at least one interaction to confirm the subject sent messages -
ArgumentMatchersused for flexible argument matching -
@Captorused when you need to assert on actual argument values - Strict stubbing passes (no unused stubs) or explicitly made lenient
- Test fails for the right reason when the subject is broken
- No shared mutable state between test methods
- No calls to real external systems from spy objects
- Real objects only used when they are simple, deterministic, and have no I/O
Interview Questions
A Mock creates a pure test double from scratch. All method implementations are replaced with stub implementations that return null, empty collections, or zero primitives by default. A Spy wraps an existing real object and delegates most calls to it unless you explicitly stub a method to return something different. Use mocks for complete isolation from collaborators. Use spies when you want real behavior for most calls but need to stub specific methods or verify certain interactions without replacing the entire object's logic.
@MockBean do in Spring Boot testing?@MockBean does two things in one. First, it creates a Mockito mock of the annotated field's type. Second, it registers that mock as a bean in the Spring Test application context, replacing any existing bean of the same type. This means your test loads a Spring slice but your controller or service under test receives the mock instead of the real collaborator, giving you full control over its behavior while still running inside the Spring context.
You use verify() combined with argument matchers. For exact values, pass them directly: verify(service).findById(1L). For flexible matching, use ArgumentMatchers: verify(service).findById(anyLong()) or verify(service).findById(argThat(id -> id > 0)). When you need the actual argument value for detailed assertions, create an ArgumentCaptor, pass it to verify, then call captor.getValue() to retrieve the actual argument after the test runs.
The most common cause is calling a method on the mock that was never stubbed. Mockito returns null for object return types by default, so if your code dereferences that null, you get a NullPointerException. Fix it by stubbing every method that returns a non-primitive type and is used by the subject under test. Enable strict stubbing mode in your test configuration to catch these cases automatically: @MockitoSettings(strictness = Strictness.STRICT_STUBS). This makes the test fail at the point of the unstubbed call rather than later at the dereference.
Mockito cannot mock final classes or final methods by default because it creates subclasses at runtime. For final classes, you need the mockito-inline extension which uses bytecode manipulation instead of subclassing. For static methods, you use mockStatic() wrapped in a try-with-resources block. The better approach is to design your code to avoid these patterns: depend on interfaces rather than concrete classes, and inject dependencies rather than calling static utility methods. This keeps your code more testable and follows SOLID principles naturally.
@InjectMocks work and when should you use it?@InjectMocks tries to inject mocks marked with @Mock into the annotated field. Mockito uses constructor injection first, then setter injection, then field injection. Use it when you want to test a class that has multiple dependencies and would otherwise need to manually instantiate it with all its mocks. However, constructor injection is generally preferred over @InjectMocks because it makes dependencies explicit in the constructor signature and works better with DI containers in production code.
when().thenReturn() and doReturn().when() syntax?Both achieve the same result, but the doReturn().when() syntax is safer for spies because when() on a spy actually invokes the real method before stubbing, which can cause side effects. doReturn() bypasses this by setting the stub directly without calling the method. Use doReturn().when() whenever you are stubbing a spy. The when().thenReturn() style reads more naturally for mocks and is more common in that context.
Use when().thenThrow() for stubs that throw checked or unchecked exceptions. For example: when(service.findById(1L)).thenThrow(new EntityNotFoundException("User not found")). For spies where you want to avoid calling the real method, use doThrow(): doThrow(new RuntimeException("error")).when(spy).someMethod(). You can also use thenThrow() chained for sequential exceptions on successive calls.
Answer interface used for in Mockito?The Answer interface lets you define custom stub behavior that goes beyond simple return values. You can compute a response based on the actual arguments passed, call real methods on a spy, log arguments for debugging, or implement conditional stubbing. Example: when(mock.getData(anyString())).thenAnswer(invocation -> invocation.getArgument(0) + "-processed"). This is useful when the stub logic depends on runtime values rather than fixed return values.
@SpyBean differ from @MockBean in Spring Boot?@MockBean creates a pure mock that replaces the real bean in the Spring context, returning default values for all methods. @SpyBean creates a spy that wraps the real bean, delegating most calls to the actual implementation while letting you stub specific methods. Use @SpyBean when you want the real bean for most operations but need to override certain behaviors, such as when testing a service that calls other real methods on its dependencies but you want to control one particular call.
InOrder verification in Mockito?InOrder verifies that method calls happened in a specific sequence, not just that they occurred. Create it with inOrder(mock1, mock2) and then call verify in order: inOrder.verify(mock1).methodA() followed by inOrder.verify(mock1).methodB(). This is useful for testing workflows where order matters, such as a checkout process where save must happen before sendConfirmation. Without InOrder, Mockito verifies each call independently.
void in Mockito?For void methods, use doNothing().when(mock).voidMethod(args) or when(mock, () -> { mock.voidMethod(args); return null; }).thenReturn(null). The simpler syntax is doNothing().when(mock).voidMethod(). If you need the void method to throw an exception, use doThrow(new Exception()).when(mock).voidMethod(). This pattern is necessary because you cannot use when(mock.voidMethod()).then... since void methods return nothing.
lenient() stubbing and when would you use it?lenient() disables strict stubbing checks for a specific stub, allowing it to exist without being invoked during the test. Use it when a stub is set up for optional behavior or when a shared base test class sets up stubs that not every subclass test uses. You can apply it per stub with lenient().when(...), per mock with lenient().doReturn..., or globally with @MockitoSettings(strictness = Strictness.LENIENT). Overusing lenient mode can hide real stubbing issues, so use it sparingly.
Use verify(mock, never()).methodName() to assert that a method was not called at all during the test. This is more expressive than checking a counter or flag manually. You can also combine it with times(0): verify(mock, times(0)).methodName() is equivalent but never() reads more clearly in test intent. For verifying no methods were called on any mock, use verifyNoInteractions(mock1, mock2).
ArgumentMatchers allow stubs and verifications to match arguments using patterns instead of exact values. Common matchers include any(), anyString(), anyLong(), eq(value), argThat(predicate), and contains(). They are useful when you want to stub a method for any argument of a certain type, or when verifying that a method was called with a value meeting a condition. However, avoid overly broad matchers like any() on all arguments as they can mask real bugs by accepting incorrect values.
verify() and verifyNoInteractions()?verify() checks a specific method call on a specific mock: verify(service).findById(1L). verifyNoInteractions() asserts that no method was called on the given mocks at all: verifyNoInteractions(mock1, mock2). Use verifyNoInteractions() when you want to ensure a certain code path did not trigger any collaborator calls, such as when a service should return early from a cache hit without calling the repository.
For testing async code, you can stub the async method to return immediately and verify the callback was invoked. Use when(mock.asyncMethod(any())).thenAnswer(invocation -> { ((Callback) invocation.getArgument(1)).onComplete(result); return null; }). Alternatively, use await() from Awaitility library to wait for async operations to complete in tests, or use Thread.sleep() with verify and timeout() to check that a method was eventually called.
Mockito uses a pluggable mock maker architecture. The default subclass-based mock maker cannot mock final classes, final methods, or static methods. To handle these, you add the mockito-inline artifact which provides a bytecode-manipulating mock maker. You configure it by adding the file mockito-extensions/org.mockito.plugins.MockMaker with content mock-maker-inline. This is needed when your codebase uses final classes or static utility methods that you need to mock in tests.
Further Reading
- Official Mockito Documentation — The definitive API reference
- Mockito GitHub Repository — Source code, release notes, and issue tracker
- Baeldung: Mockito Tutorial — Comprehensive guide from basics to advanced usage
- Spring Boot Testing Documentation — Official docs on @MockBean and test slices
- Martin Fowler: Test Double — Conceptual foundation for mocks, spies, and other test doubles
- JMockit vs Mockito vs EasyMock — Comparison of Java mocking frameworks
- Testcontainers — For integration tests needing real databases in containers
- WireMock — For stubbing HTTP responses in integration tests
Conclusion
Mockito is the backbone of Java unit testing — it lets you isolate the class under test by replacing its dependencies with controlled stubs and mocks. The core workflow is straightforward: create mocks, stub their methods to return specific values, optionally verify those methods were called, then let JUnit drive the assertions. Where Mockito really earns its place is in preventing test brittleness — mocking at the interface level rather than concrete classes keeps your tests stable even when internal implementation changes.
Category
Related Posts
Spring Boot Build Tools: Maven & Gradle
Configure Maven and Gradle for Spring Boot projects—plugins, dependency management, packaging JARs and WARs, and build automation essentials.
Embedded Web Servers in Spring Boot: Tomcat, Jetty, Undertow
Configure embedded servers in Spring Boot: compare Tomcat, Jetty, and Undertow, tune thread pools, enable access logs, and switch implementations.
JUnit 5 & Jupiter: Lifecycle, Nested & Parameterized Tests
Explore JUnit 5 Jupiter features: master test lifecycle annotations, organize tests with @Nested, and parameterize tests with @CsvSource and @MethodSource.