Mockito Archive

Mockito is one of the most widely used mocking frameworks in the Java ecosystem. It creates test doubles for collaborators, allowing unit tests to run without invoking databases, network services, queues, or other unstable dependencies. This article serves as a practical archive of Mockito’s core APIs, JUnit 5 integration, static mocking, version considerations, and maintenance practices.
For a Maven project, mockito-core is the primary dependency. When using JUnit 5, add mockito-junit-jupiter to enable the Mockito extension and annotations:

<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-core</artifactId>
  <version>5.12.0</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-junit-jupiter</artifactId>
  <version>5.12.0</version>
  <scope>test</scope>
</dependency>

Mockito 5 requires Java 11 or later. Mockito 4 remains useful for projects that must maintain Java 8 compatibility.

Spring Boot’s spring-boot-starter-test already includes Mockito, so an additional dependency is usually unnecessary. In a multi-module Maven project, import mockito-bom to keep the Mockito version consistent across modules:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.mockito</groupId>
      <artifactId>mockito-bom</artifactId>
      <version>5.12.0</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

Modules can then declare mockito-core and mockito-junit-jupiter without individual version numbers.

Creating Mocks and Defining Behavior

Use mock() to create a test double:

UserRepository repository = mock(UserRepository.class);

when(repository.findById(1L))
    .thenReturn(Optional.of(new User(1L, "Alice")));

Common stubbing patterns include:

  • thenReturn(value) returns a fixed value.
  • thenThrow(exception) raises an exception.
  • thenAnswer(invocation -> ...) computes a result from the invocation.
  • thenReturn(1, 2, 3) returns a different value on each successive call.

Dynamic answers are especially useful when the result depends on method arguments:

when(repository.findByUsername(anyString()))
    .thenAnswer(invocation -> {
        String username = invocation.getArgument(0);
        return Optional.of(new User(10L, username));
    });

For void methods, use doThrow(), doNothing(), or doAnswer():

doThrow(new IllegalStateException("Account is locked"))
    .when(repository)
    .deleteById(1L);

The doReturn() form is also appropriate when working with spies or overloaded methods where when() may be inconvenient.

Verifying Interactions

Mockito can verify not only return values but also how a collaborator was used:

verify(repository).findById(1L);
verify(repository, times(1)).findById(1L);
verify(repository, never()).deleteById(anyLong());

verify(repository) implies exactly one matching invocation. Other useful verification modes include atLeastOnce(), atLeast(), and never().

Mockito also provides two broad checks:

verifyNoInteractions(repository);
verifyNoMoreInteractions(repository);

verifyNoInteractions() confirms that the mock was never used. verifyNoMoreInteractions() confirms that no calls occurred beyond those already verified. The latter can make tests fragile, so it should be applied selectively to collaborators whose complete interaction contract matters.

Argument matchers make verification more flexible:

verify(repository).save(eq("Alice"), anyInt());
verify(repository).findById(anyLong());

If any argument in a call uses a matcher, all arguments must use matchers. Mixing raw values with matchers can produce confusing errors or incorrect verification behavior.

JUnit 5 Integration

MockitoExtension manages mock initialization and integrates Mockito with the JUnit 5 test lifecycle:

@ExtendWith(MockitoExtension.class)
class OrderServiceTest {

    @Mock
    private PaymentGateway paymentGateway;

    @InjectMocks
    private OrderService orderService;

    @Test
    void shouldPaySuccessfully() {
        when(paymentGateway.pay(anyString(), eq(100.0)))
            .thenReturn(new PaymentResult(true, "OK"));

        OrderResult result = orderService.pay("order-1", 100.0);

        assertTrue(result.success());
        verify(paymentGateway).pay("order-1", 100.0);
    }
}

The most frequently used annotations are:

  • @Mock creates a mock instance.
  • @InjectMocks injects available mocks into the tested object.
  • @Captor creates an ArgumentCaptor.
  • @Spy wraps or creates an instance that delegates selected calls to a real implementation.

Mockito’s JUnit 5 extension enables strict stubbing by default. A stub that is never used may result in an UnnecessaryStubbingException. This behavior generally exposes stale or irrelevant setup, but lenient() can be used when a stub is valid yet intentionally unused in a particular test:

lenient()
    .when(repository.findById(anyLong()))
    .thenReturn(Optional.empty());

Use lenient() sparingly; removing unnecessary setup is usually the cleaner solution.

Argument Capturing

ArgumentCaptor retrieves argument values that were passed to a mocked method:

@Captor
ArgumentCaptor<String> orderIdCaptor;

@Test
void shouldForwardTheOrderId() {
    orderService.pay("order-1", 100.0);

    verify(paymentGateway).pay(orderIdCaptor.capture(), eq(100.0));
    assertEquals("order-1", orderIdCaptor.getValue());
}

For multiple invocations, getAllValues() returns the captured arguments in order. Captors are useful for inspecting complex objects or individual values, but direct verification with known arguments is usually simpler.

BDD-Style Tests

Behavior-Driven Development style separates the arrangement of behavior from the verification of that behavior. The given, when, and then phases can make tests easier to scan:

@Test
void shouldLoadTheUser() {
    given(repository.findById(1L))
        .willReturn(Optional.of(new User(1L, "Alice")));

    when(userService.load(1L))
        .thenReturn(Optional.of(new User(1L, "Alice")));

    then(repository).should().findById(1L);
}

BDD syntax uses the same underlying Mockito engine as when().thenReturn() and verify(). It is primarily a readability choice rather than a different testing model.

Static Methods and Final Classes

Mockito 5 uses the inline mock maker by default. It can mock static methods, final classes, and other constructs that older mock makers could not handle directly:

try (MockedStatic<LocalDate> mocked = mockStatic(LocalDate.class)) {
    mocked.when(LocalDate::now)
        .thenReturn(LocalDate.of(2025, 1, 1));

    // Exercise the code under test.
}

A static mock must always be closed. The try-with-resources pattern guarantees cleanup even if the test fails. Static mocking is thread-local, and an active static mock must not remain open when the test suite moves on to another test.

In Mockito 4, the inline mock maker may require the separate mockito-inline artifact. When migrating to Mockito 5, that artifact should normally be removed because the default mock maker already uses inline instrumentation. Keeping both can lead to conflicting mock-maker configurations.

Static mocks should be reserved for genuinely uncontrollable dependencies such as the current time. If a class is difficult to mock because it performs too many unrelated operations, extracting a dependency is often a better design improvement than surrounding the test with static mocking.

Migrating from Mockito 4 to Mockito 5

A migration should focus on the following changes:

  1. Upgrade the Java baseline. Mockito 5 requires Java 11, while Mockito 4 supports Java 8.
  2. Remove obsolete inline configuration. Delete a dedicated mockito-inline dependency if the project moves entirely to Mockito 5.
  3. Review strict stubbing. The JUnit 5 extension may reveal unused stubs that previous tests tolerated.
  4. Update deprecated initialization patterns. Prefer MockitoExtension with @Mock instead of manually initializing mocks in each test.
  5. Recheck final and static mocking. Behavior that previously required special configuration may now work directly.
  6. Retest the full suite. Changes to the mock maker can affect framework integrations, especially those involving instrumentation or class loading.

Version upgrades should be performed independently from unrelated production-code changes. This makes it easier to identify whether a failure comes from the application or from Mockito’s runtime behavior.

Practical Guidelines

A healthy Mockito test suite follows several principles:

  • Mock collaborators, not values. Simple data objects and value types are usually clearer and safer as real instances.
  • Prefer behavioral assertions. Assert the returned result first, then verify only the interactions that are relevant to the contract.
  • Avoid excessive verification. Tests that verify every method call often become coupled to implementation details.
  • Do not share mutable mocks between tests. Let the JUnit extension create fresh instances for each test method.
  • Keep static mocks narrow. Scope them to the smallest possible block and always close them promptly.
  • Avoid broad verifyNoMoreInteractions() checks. Unnecessary calls may be harmless implementation details.
  • Use real fakes for stateful behavior. An in-memory repository can sometimes be more realistic and maintainable than a large network of mocks.
  • Treat excessive mocking as a design signal. A class with too many dependencies may be violating separation-of-concerns principles.

Quick Reference

Task Recommended API
Create a mock mock(Type.class)
Return a value when(...).thenReturn(...)
Throw an exception when(...).thenThrow(...)
Configure a void method doThrow(...).when(mock).method()
Verify one call verify(mock).method()
Verify no calls verify(mock, never()).method()
Capture an argument ArgumentCaptor
Initialize with JUnit 5 @ExtendWith(MockitoExtension.class)
Inject mocks @InjectMocks
Mock a static method mockStatic(...)
Mock a final class Standard mock(...) API in Mockito 5

Used with restraint, Mockito provides precise control over dependencies while keeping unit tests fast, deterministic, and easy to diagnose. The most maintainable tests rely on mocks to clarify boundaries, not to reproduce the entire implementation in miniature.