पाठ 14 / 25

Spies, Static and Final Mocking

Use spies for partial mocking and mock static methods and final classes when unavoidable.

Powerful tools, used sparingly

A spy (spy(realObject) or @Spy) wraps a real object: unstubbed methods run the real code, while selected methods can be stubbed and all calls can be verified. Spies help with legacy code that is hard to restructure, but needing them often signals that the class does too much. Stub spies with doReturn(x).when(spy).method(), because when(spy.method()) would call the real method during stubbing. With Mockito 5's default inline mock maker, you can also mock final classes and methods and, with Mockito.mockStatic(Type.class), static methods inside a try-with-resources block that limits the scope. Use these features to cope with code you cannot change (third-party utilities, legacy statics), not as a design default: static calls such as LocalDate.now() or UUID.randomUUID() inside business logic are better replaced by injected collaborators (Clock, an ID generator). On recent JDKs, Mockito recommends configuring it as a Java agent in the build so the inline mock maker does not rely on dynamic agent loading, which newer JDKs warn about.

A spy, a static mock and a better design

mockStatic is scoped; injecting a Clock removes the need for it.

@Test
void spyStubsOneMethodOnly() {
    List<String> real = new ArrayList<>(List.of("a", "b"));
    List<String> spy = spy(real);
    doReturn(100).when(spy).size();           // stub one method; others are real
    spy.add("c");
    assertThat(spy.size()).isEqualTo(100);
    assertThat(spy.get(2)).isEqualTo("c");
}

@Test
void staticMockWhenCodeCannotChange() {
    try (MockedStatic<LegacyIds> ids = mockStatic(LegacyIds.class)) {
        ids.when(LegacyIds::next).thenReturn("ID-42");
        assertThat(new LegacyInvoiceFactory().create().id()).isEqualTo("ID-42");
    }   // static behaviour restored here
}

// better design: inject time instead of calling LocalDate.now() inside the logic
final class DueDateCalculator {
    private final Clock clock;
    DueDateCalculator(Clock clock) { this.clock = clock; }
    LocalDate dueDate(int days) { return LocalDate.now(clock).plusDays(days); }
}

@Test
void dueDateIsDeterministicWithFixedClock() {
    Clock fixed = Clock.fixed(Instant.parse("2026-10-03T00:00:00Z"), ZoneOffset.UTC);
    assertThat(new DueDateCalculator(fixed).dueDate(30)).isEqualTo(LocalDate.of(2026, 11, 2));
}

Inject Clock for time-based logic

Code that calls LocalDate.now() directly produces tests that fail at midnight or month end. Passing a java.time.Clock makes time a normal dependency you can fix in tests.

त्वरित जाँच: Why use doReturn(...).when(spy).method() instead of when(spy.method()).thenReturn(...) for spies?

  • doReturn is faster
  • when() does not work with Mockito 5
  • doReturn verifies the call
  • when(spy.method()) calls the real method during stubbing, which may have side effects or fail
Answer

when(spy.method()) calls the real method during stubbing, which may have side effects or fail — Spies run real code, so the doReturn style avoids executing the real method while stubbing.