Mockito Tutorial: Mocking in Java

Mockito Tutorial: Mocking in Java

Unit tests are supposed to test one thing in isolation. The moment your UserService needs a live database connection, a real email server, or a third-party HTTP client, your "unit" test becomes an integration test — slow, brittle, and hard to run in CI. Mockito solves this by letting you replace real dependencies with controlled fakes called mocks.

This tutorial covers everything you need to write clean, reliable unit tests in Java using Mockito with JUnit 5.

What Is Mockito and Why Mock?

Mockito is an open-source Java mocking framework. It creates fake implementations of interfaces and classes at runtime, letting you control their behavior and verify how your code interacts with them.

Why mock?

  • Speed — no database round-trips, no HTTP calls, tests run in milliseconds
  • Isolation — failures point to the unit under test, not a flaky dependency
  • Control — simulate error conditions (timeouts, exceptions) that are hard to reproduce with real systems
  • No setup cost — no test containers, no embedded databases, just your test class

Maven and Gradle Setup

Mockito 5.x works with JUnit 5 out of the box. Add the unified artifact that bundles both:

Maven:

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

Gradle:

testImplementation 'org.mockito:mockito-junit-jupiter:5.11.0'

This single dependency pulls in mockito-core and the JUnit 5 extension. No extra configuration needed.

Creating Mocks

There are two ways to create a mock: programmatically and via annotations.

Programmatic — Mockito.mock():

import static org.mockito.Mockito.*;

UserRepository repo = mock(UserRepository.class);

Good for one-off mocks or when you need a mock outside a test class.

Annotation-based — @Mock with @ExtendWith:

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    UserRepository userRepository;

    @Mock
    EmailClient emailClient;

    // tests here
}

MockitoExtension initializes all @Mock fields before each test and resets them after. This is the preferred style — less boilerplate, automatic cleanup.

Stubbing with when().thenReturn() and when().thenThrow()

A freshly created mock returns safe defaults: null for objects, 0 for numbers, false for booleans, empty collections for lists. To make a mock return something specific, stub it.

Returning a value:

@Test
void returnsUserById() {
    User alice = new User(1L, "Alice");
    when(userRepository.findById(1L)).thenReturn(Optional.of(alice));

    Optional<User> result = userService.getUser(1L);

    assertTrue(result.isPresent());
    assertEquals("Alice", result.get().getName());
}

Chaining multiple return values — each call gets the next value; the last one repeats:

when(userRepository.countActive())
    .thenReturn(5)
    .thenReturn(6);

Throwing an exception:

@Test
void throwsWhenDatabaseIsDown() {
    when(userRepository.findById(anyLong()))
        .thenThrow(new DataAccessException("connection refused"));

    assertThrows(ServiceException.class, () -> userService.getUser(99L));
}

Use thenThrow to test your error-handling paths — the kind of scenario you cannot reliably reproduce against a real database.

Verifying Interactions

Stubbing controls what a mock returns. Verification confirms your code actually called the mock in the expected way.

@Test
void sendsWelcomeEmailOnRegistration() {
    userService.register("alice@example.com", "Alice");

    verify(emailClient).sendWelcome("alice@example.com");
}

Checking call counts with times():

verify(emailClient, times(1)).sendWelcome("alice@example.com");
verify(userRepository, times(2)).save(any(User.class));

Asserting a method was never called:

@Test
void doesNotSendEmailWhenRegistrationFails() {
    when(userRepository.save(any())).thenThrow(new DuplicateKeyException("exists"));

    assertThrows(RegistrationException.class,
        () -> userService.register("alice@example.com", "Alice"));

    verify(emailClient, never()).sendWelcome(anyString());
}

never() is one of the most valuable verifications — it guards against side effects that should not happen on the failure path.

@InjectMocks — Wiring Mocks Into the Class Under Test

Manually constructing your service and passing mocks is tedious. @InjectMocks does it for you:

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    UserRepository userRepository;

    @Mock
    EmailClient emailClient;

    @InjectMocks
    UserService userService;  // Mockito injects the two mocks above

    @Test
    void deactivatesUser() {
        User user = new User(42L, "Bob");
        when(userRepository.findById(42L)).thenReturn(Optional.of(user));

        userService.deactivate(42L);

        verify(userRepository).save(user);
        assertFalse(user.isActive());
    }
}

Mockito tries constructor injection first, then setter injection, then field injection. Constructor injection is the safest — it makes dependencies explicit and works with final fields.

Argument Matchers

Sometimes you care about behavior regardless of the exact argument value. Mockito provides matchers for this:

// Match any long
when(userRepository.findById(anyLong())).thenReturn(Optional.empty());

// Match any non-null string
when(emailClient.sendWelcome(anyString())).thenReturn(true);

// Match a specific value precisely
when(userRepository.findByEmail(eq("admin@example.com"))).thenReturn(Optional.of(admin));

// Match objects satisfying a predicate
when(userRepository.save(argThat(u -> u.getName().startsWith("A")))).thenReturn(savedUser);

Important rule: if you use matchers in a stubbing or verification, every argument must be a matcher. You cannot mix literals and matchers in the same call:

// WRONG — will throw InvalidUseOfMatchersException
verify(emailClient).send("alice@example.com", anyString());

// CORRECT
verify(emailClient).send(eq("alice@example.com"), anyString());

Spies — Partial Mocking with Mockito.spy()

A spy wraps a real object. Real methods execute unless you explicitly stub them. Use spies when you need to test a class that calls its own methods, or when creating a full mock of a complex class is impractical.

@Test
void spyCallsRealMethodUnlessStubbed() {
    List<String> realList = new ArrayList<>();
    List<String> spy = spy(realList);

    spy.add("hello");
    spy.add("world");

    // real method called — list has 2 elements
    assertEquals(2, spy.size());

    // stub a specific method
    doReturn(99).when(spy).size();
    assertEquals(99, spy.size());
}

Prefer doReturn().when() over when().thenReturn() with spies — the latter calls the real method during stubbing, which can cause unexpected side effects.

Use spies sparingly. If you find yourself needing to spy on the class under test, it often signals the class is doing too much and should be split.

Capturing Arguments with ArgumentCaptor

Sometimes you need to inspect the exact object that was passed to a mock — not just that it was called, but what data it carried.

@Test
void savesUserWithCorrectRole() {
    ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);

    userService.register("carol@example.com", "Carol");

    verify(userRepository).save(captor.capture());
    User saved = captor.getValue();

    assertEquals("carol@example.com", saved.getEmail());
    assertEquals(Role.MEMBER, saved.getRole());
    assertNotNull(saved.getCreatedAt());
}

With JUnit 5 and the @Mock extension, you can use the @Captor annotation instead:

@Captor
ArgumentCaptor<User> userCaptor;

ArgumentCaptor shines when the object passed to a dependency is constructed inside the method under test and has no other way to be observed.

Common Mistakes and Best Practices

Mistake: Mocking value objects. Don't mock String, Integer, LocalDate, or simple data classes. Instantiate them directly — mocking them adds complexity with no benefit.

Mistake: Over-verifying. Testing that every single mock interaction happened turns tests into brittle implementation mirrors. Verify interactions that matter for the behavior you're testing; skip internal implementation details.

Mistake: Stubbing what you don't need. If a stub is never called during the test, it's noise. Mockito's strict stubbing mode (the default with MockitoExtension) flags unused stubs as errors — treat them as a signal to clean up your test.

Mistake: mock(ConcreteClass.class) on everything. Prefer mocking interfaces. Mocking concrete classes requires subclassing at runtime and breaks when the class has final methods. Design your code around interfaces or use mockito-subclass for classes you don't own.

Best practice: One behavior per test. Each test should stub what it needs and verify one outcome. Tests that set up five stubs and make three assertions are testing too much at once — split them.

Best practice: Name tests after behavior, not methods. sendsWelcomeEmailOnRegistration is more useful than testRegister. When a test fails, the name should tell you what broke.

Best practice: Reset state between tests. MockitoExtension resets mocks automatically between tests — rely on it, don't share mock state across tests through instance fields that you mutate.

Test the Full Stack

Mockito isolates your Java units. For end-to-end browser tests that verify real user workflows, HelpMeTest runs AI-generated scenarios with 24/7 monitoring — no mocking required.

Start testing free →

Read more