JUnit Mockito — Cheat Sheet

Tags: java, junit, mockito, testing, tdd, unit-testing

Level: intermediate

TL;DR: Best practices, annotations, stubbing, and verification patterns for isolated Java unit tests using JUnit 5 and Mockito.

Key concepts

JUnit 5 separates API (junit-jupiter-api) from runtime engine (junit-jupiter-engine).

Mockito creates dynamic test doubles to isolate classes under test from external dependencies.

@ExtendWith(MockitoExtension.class) initializes mocks and manages lifecycle in JUnit 5.

Mocks return default dummy values (null, 0, false, empty collection) unless stubbed.

Spies wrap real objects, delegating method calls unless explicitly stubbed.

when(...).thenReturn(...) defines deterministic behavior for mocked method invocations.

verify(mock, times(n)).method(...) asserts interaction count and arguments passed to collaborators.

Argument matchers like any() or eq() require all method arguments to use matchers.

ArgumentCaptor captures actual arguments passed to mocks for detailed assertions.

Fast execution and test isolation prevent flaky builds and database/network side effects.

Typical questions and ready answers

Q: What is the difference between @Mock and @Spy?R: I use @Mock to create a complete dummy object with no real logic, while @Spy wraps an existing real instance, executing actual code unless I stub specific methods.

Q: How do you test a void method with Mockito?R: For side-effect verification, I use verify(mock).voidMethod(). If the void method must throw an exception or do nothing, I stub it using doThrow() or doNothing().

Q: Why prefer doReturn().when() over when().thenReturn() on spies?R: With when(spy.call()).thenReturn(), the real method executes once during stub definition. doReturn().when(spy).call() avoids calling the real logic during setup.

Pitfalls and common mistakes

Mixing raw values and matchers in the same invocation (e.g. mock.call(any(), "raw") throws InvalidUseOfMatchersException).

Mocking value objects, DTOs, or standard library collections instead of instantiating real objects.

Overusing @Spy or partial mocks, which often signals high coupling or poor class design.

Asserting implementation details instead of verifiable state or interface boundaries.

Forgetting that Mockito cannot mock static methods, private methods, or final classes without mockito-inline.

Snippets and commands

@ExtendWith(MockitoExtension.class)
class OrderServiceTest {

    @Mock
    private PaymentClient paymentClient;

    @Mock
    private OrderRepository orderRepository;

    @InjectMocks
    private OrderService orderService;

    @Captor
    private ArgumentCaptor<Order> orderCaptor;

    @Test
    void shouldProcessOrderSuccessfully() {
        Order inputOrder = new Order("ORD-1", 100.0);
        when(paymentClient.authorize(anyString(), anyDouble())).thenReturn(true);
        when(orderRepository.save(any(Order.class))).thenAnswer(i -> i.getArgument(0));

        boolean result = orderService.process(inputOrder);

        assertTrue(result);
        verify(orderRepository).save(orderCaptor.capture());
        assertEquals("PROCESSED", orderCaptor.getValue().getStatus());
        verify(paymentClient, times(1)).authorize(eq("ORD-1"), eq(100.0));
    }

    @Test
    void shouldHandlePaymentFailure() {
        doThrow(new PaymentException("Network error"))
            .when(paymentClient).authorize(anyString(), anyDouble());

        assertThrows(PaymentException.class, () -> orderService.process(new Order("ORD-2", 50.0)));
        verifyNoInteractions(orderRepository);
    }
}

To review

[ ] ArgumentCaptor vs ArgumentMatcher usage rules

[ ] BDDMockito syntax (given / then) vs standard syntax

[ ] Strict stubbing behavior in JUnit 5 MockitoExtension

[x] Basic annotations: @Mock, @InjectMocks, @Spy

[x] Assertion methods in org.junit.jupiter.api.Assertions

Jarvis Interviews
Jarvis