View all services
Talk to QA Advisor
Browse the Knowledge Hub211 resources
/Tools Library/Mockito
Tools Library · Review and guide

Mockito review: tutorial, pros and cons

Mockito is the standard mocking library for Java and the JVM. It creates mocks and spies, stubs their responses with when and thenReturn, and verifies interactions afterwards, with argument captors and matchers for detail. Mockito 5 needs Java 11, uses the inline mock maker by default and, through its JUnit extension, fails tests that carry unused stubs. It is free under the MIT licence.

QAble has evaluated this tool and works alongside it. The factual sections are complete; our view is bounded to what we have actually seen rather than a long-term verdict.

last reviewed
13 October 2026
version covered
Mockito 5
licence
Open source

Reviewed by Viral Patel

Co-Founder, QAbleLast reviewed 13 October 2026

At a glance

Mockito at a glance

Official site
Vendor
Mockito project
Category
Unit testing
Licence
Open source (MIT)
Platforms
JVM
Languages
Java, Kotlin
Integrates with
JUnit, TestNG, Spring
First released
2008
Version reviewed
Mockito 5

Verdict

Our verdict on Mockito

Strengths

  • Readable API: when(...).thenReturn(...) to stub, verify(...) to check, close to how the test is described
  • Strict stubs in MockitoExtension fail tests with unused stubs, catching tests that no longer match the code
  • Final classes, final methods and enums can be mocked by default since 5.0
  • mockStatic and mockConstruction cover legacy code with static calls or hard-wired object creation
  • Argument captors and matchers check what a collaborator actually received
  • Free under MIT, with optional commercial support through Tidelift

Weaknesses

  • On Java 21 and later the inline mock maker needs the agent configured in the build, or the JVM warns
  • Makes it easy to verify implementation details, which produces brittle tests that break on refactoring
  • Matchers must be used for all arguments or none in a call, a rule that trips newcomers
  • Kotlin support needs the separate mockito-kotlin helper library, and MockK is often a better fit

Our take

Mockito is the default mocking choice on the JVM, and strict stubbing through MockitoExtension is the feature that makes it worth using well: a test that stubs calls the code never makes fails, which usually means the test has drifted from the code it claims to cover. QAble has evaluated it rather than maintained a long-running suite on it, so treat this as a bounded take. The risk with Mockito is not the library but over-use. Tests that mock every collaborator and verify every call pin the implementation rather than the behaviour, so correct refactoring breaks them. Mock what you do not own or cannot run cheaply, such as gateways and clocks, and use real objects or fakes elsewhere. On Java 21 and later, set up the Mockito agent explicitly rather than ignoring the JVM warning.

We have evaluated this tool rather than maintained a long-running suite on it, so this is a bounded view rather than a long-term verdict. The rest of this page is factual.

How it works

What is Mockito and how does it work?

Bytecode instrumentation through an agent

Mockito creates mocks with Byte Buddy. Since version 5 the default is the inline mock maker, which uses a Java agent to rewrite the bytecode of the mocked class in place rather than generating a subclass. That is why final classes, final methods and static methods can be mocked without extra setup. It is also why Java 21 matters: the JDK now restricts libraries from attaching an agent to their own JVM at run time, so Mockito prints a warning and future JDKs may refuse. The documented fix is to pass mockito-core as a -javaagent to the test JVM through Maven or Gradle.

Stubs record answers, verification reads calls

A mock records every call made to it. when(mock.method(args)).thenReturn(value) registers an answer for matching calls; unstubbed methods return empty values such as null, zero, false, empty collections or, since 5.21, completed futures. verify(mock).method(args) then checks the record after the code under test has run. Argument matchers such as any(), eq() and argThat() widen what counts as a match, and an ArgumentCaptor stores the actual arguments so the test can assert on them. Stubbing answers the question of what a collaborator returns; verification answers what the code told the collaborator to do.

Strictness decides what fails

Plain Mockito is lenient: an unused stub or an unexpected argument passes silently. MockitoExtension, from the mockito-junit-jupiter artifact, defaults to STRICT_STUBS, which fails the test with UnnecessaryStubbingException when a stub is never used and reports a potential stubbing problem when a stubbed method is called with different arguments. That catches tests that drifted, and copied setup that does nothing. @MockitoSettings changes the level for a class, and lenient() exempts one stub where sharing setup is deliberate.

Scoped static and construction mocks

mockStatic and mockConstruction return a scoped object, MockedStatic or MockedConstruction, that must be closed, normally with try-with-resources. While open, static calls on that class, or constructors of that type, are intercepted on the current thread only, and closing restores the real behaviour. This scoping is what keeps static mocking from leaking into other tests, and why forgetting to close it produces errors in unrelated tests later. Version 5.24 added spyStatic, which keeps the real static methods while letting specific ones be stubbed.

Strengths

What it does well

Annotations that remove boilerplate

@Mock, @Spy, @Captor and @InjectMocks declare test doubles as fields, and MockitoExtension creates and injects them before each test. @InjectMocks builds the class under test through its constructor, setters or fields, so most tests need no manual wiring.

Argument captors

ArgumentCaptor captures the arguments a mock received, so a test can assert on a complex object, such as the email built by a notification service, rather than matching it inside verify. It is the clearest way to check outputs that leave the class through a collaborator.

Spies for partial mocking

A spy wraps a real object: unstubbed methods run the real code, and selected methods can be stubbed or verified. Useful for legacy classes that cannot be split, with doReturn(...).when(spy) syntax to avoid calling the real method while stubbing.

BDD-style aliases

BDDMockito offers given(...).willReturn(...) and then(mock).should(), which read naturally in given, when, then structured tests. They are aliases over the same engine, so teams can choose the style without changing behaviour.

Mocking types that used to be off limits

With the inline mock maker as default, final classes, final methods, enums and, since 5.22, singletons such as Kotlin objects can be mocked. Mockito 5.23 brought final and Kotlin class mocking to mockito-android, which now requires API level 28 or later.

Verification of order and absence

InOrder checks that calls happened in a given sequence across one or several mocks, verify with never() asserts a call did not happen, and verifyNoMoreInteractions fails on unexpected extra calls. Use them sparingly, where the order or absence is the behaviour being specified.

Pricing

Mockito pricing and licensing

Mockito is free and open source under the MIT licence, maintained by its contributors with no paid edition and no feature held back. Organisations that need a commercial agreement can buy support through Tidelift, which the Mockito README links to. The practical costs are elsewhere: build configuration for the Java 21 agent, and the maintenance cost of tests that mock too much, which is the main way a Mockito suite becomes expensive.

  • Licence: MIT for mockito-core and its official artifacts, stated in the mockito/mockito repository.
  • No tiers: static, construction and final mocking, strict stubs and the JUnit extension are all free on Maven Central.
  • Commercial support: through a Tidelift subscription, priced by Tidelift rather than by the project.
  • Ecosystem: mockito-junit-jupiter, mockito-kotlin and mockito-android are separate free artifacts; the Kotlin helpers come from a separate project in the Mockito organisation.
  • Hidden cost: brittle tests. Heavy verification of internal calls makes refactoring expensive, which outweighs anything the library itself costs.

Checked against the vendor’s own pages on 13 October 2026. Pricing changes; confirm on the official site before buying.

Getting started

Mockito tutorial: getting started

Setup, step by step

  1. Use Java 11 or later; Mockito 5 does not run on Java 8.
  2. Add mockito-core and mockito-junit-jupiter with test scope, alongside JUnit Jupiter.
  3. On Java 21 or later, configure mockito-core as a -javaagent for the test JVM.
  4. Annotate the test class with @ExtendWith(MockitoExtension.class).
  5. Declare collaborators with @Mock and the class under test with @InjectMocks.
  6. Stub with when, run the code, then assert on results and verify key interactions.

Install

<!-- pom.xml, test scope -->
<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-core</artifactId>
  <version>5.24.0</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-junit-jupiter</artifactId>
  <version>5.24.0</version>
  <scope>test</scope>
</dependency>

A first example

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class) // strict stubs by default
class OrderServiceTest {

    @Mock OrderRepository orders;
    @Mock PaymentGateway payments;
    @InjectMocks OrderService service; // built with the mocks above

    @Test
    void paysForAnOpenOrder() {
        when(orders.findById(42L)).thenReturn(Optional.of(new Order(42L, 4200)));
        when(payments.charge(4200)).thenReturn(Receipt.ok("ch_1"));

        Receipt receipt = service.pay(42L);

        assertEquals("ch_1", receipt.id());
        verify(orders).markPaid(42L);
    }

    @Test
    void doesNotChargeAMissingOrder() {
        when(orders.findById(7L)).thenReturn(Optional.empty());

        service.pay(7L);

        verify(payments, never()).charge(any(Integer.class));
    }
}

What trips people up first

UnnecessaryStubbingException on a test that otherwise passes. MockitoExtension uses strict stubs, so a when(...) that the code under test never triggers fails the test. It usually means the stub is copied setup, or the code takes a different path than the test assumes. Delete the stub, or move it into the tests that need it, rather than switching the class to lenient. The second early surprise is the JVM warning about a dynamically loaded agent on Java 21 or later: it is Mockito’s inline mock maker attaching itself, and the fix is to configure the agent in the build.

In practice

Using Mockito in practice

Configuring the Mockito agent for Java 21 and later

The Mockito javadoc documents the setup: resolve the mockito-core jar path with the Maven dependency plugin’s properties goal, then pass it to Surefire as a -javaagent. Keep @{argLine} at the front so other tools that set argLine, such as JaCoCo, still work, and define an empty argLine property in the POM if nothing else sets it, because the late-bound reference otherwise breaks the forked JVM. Gradle users create a separate configuration for the agent jar and add it to the test task’s JVM arguments, as the javadoc shows. The -XX:+EnableDynamicAgentLoading flag also silences the warning, but the javadoc notes it is not standardised and a future JDK may drop it.

<!-- pom.xml: from the Mockito javadoc, section 0.3 -->
<properties>
  <argLine></argLine> <!-- avoids a broken @{argLine} if nothing sets it -->
</properties>
<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-dependency-plugin</artifactId>
      <executions>
        <execution>
          <goals>
            <!-- exposes ${org.mockito:mockito-core:jar} as a property -->
            <goal>properties</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-surefire-plugin</artifactId>
      <configuration>
        <argLine>@{argLine} -javaagent:${org.mockito:mockito-core:jar}</argLine>
      </configuration>
    </plugin>
  </plugins>
</build>

Capturing what a collaborator received

When the output of a method is an object passed to a collaborator, such as an email handed to a mailer, capture it and assert on it directly. That produces clearer failures than a complex matcher inside verify. Declare captors with @Captor so generic types work without casts. Prefer one verify per behaviour you care about, and avoid verifyNoMoreInteractions as a habit: it fails whenever the code legitimately makes a new call, which is exactly the brittleness that gives mocking a bad name.

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class WelcomeServiceTest {

    @Mock Mailer mailer;
    @InjectMocks WelcomeService service;
    @Captor ArgumentCaptor<Email> email; // typed, no unchecked cast

    @Test
    void sendsAPersonalisedWelcome() {
        service.welcome(new Customer("Ada", "[email protected]"));

        verify(mailer).send(email.capture());
        assertThat(email.getValue().to()).isEqualTo("[email protected]");
        assertThat(email.getValue().subject()).contains("Ada");
    }
}

Static and construction mocks for legacy code

Static calls to a utility class, or objects created with new inside a method, cannot be replaced by injection. mockStatic and mockConstruction handle them, scoped to a try-with-resources block and to the current thread. Use them as a bridge while refactoring towards injected dependencies, not as a design: a clock or ID generator passed in through the constructor is simpler to test and needs no instrumentation. Always close the scope, since a static mock left open affects every later call on that thread.

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mockStatic;

import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;

class InvoiceNumberTest {

    @Test
    void usesTheNextSequenceNumber() {
        // The static mock is active only inside this block, on this thread
        try (MockedStatic<Sequences> seq = mockStatic(Sequences.class)) {
            seq.when(() -> Sequences.next("invoice")).thenReturn(1042L);

            assertEquals("INV-1042", InvoiceNumbers.create());

            seq.verify(() -> Sequences.next("invoice"));
        }
        // Outside the block, Sequences.next runs the real code again
    }
}

Deciding what to mock

Mock at boundaries you do not own or cannot run cheaply: payment gateways, HTTP clients, message brokers, clocks, random sources. Use real objects for value types, collections and simple domain classes, and consider an in-memory fake for repositories, which keeps tests readable when many methods are involved. For HTTP dependencies, a WireMock server often tests more than a mocked client does, because serialisation and error handling run for real. Write assertions against results and state first, and add verify only for side effects that are the point of the method, such as sending an email or publishing an event.

Keeping a Mockito suite healthy

Leave strict stubs on and treat each UnnecessaryStubbingException as a signal rather than noise. Keep shared stubbing in a @BeforeEach only where every test uses it, and mark deliberately shared stubs lenient() individually instead of loosening the whole class. Replace APIs removed in Mockito 4, which older code and tutorials still use, and replace MockitoAnnotations.initMocks with openMocks or, better, the extension. When a test needs more than a handful of mocks, take it as a design signal: the class under test probably has too many collaborators.

Pitfalls

Common pitfalls

Mixing raw values and matchers in one call

Why it happens: Mockito requires either all arguments as matchers or none. when(repo.find(any(), 42)) throws InvalidUseOfMatchersException, often reported at a confusing location.

The fix: Wrap raw values with eq(), as in when(repo.find(any(), eq(42))), or use raw values for every argument.

Using APIs removed in Mockito 4

Why it happens: The org.mockito.Matchers class, anyObject, anyVararg, verifyZeroInteractions and the org.mockito.runners package were removed in 4.0 but appear in older tutorials and code.

The fix: Use ArgumentMatchers, any(), verifyNoInteractions or verifyNoMoreInteractions, and org.mockito.junit.MockitoJUnitRunner, or move to MockitoExtension on JUnit Jupiter.

Ignoring the dynamic agent warning on Java 21 and later

Why it happens: The inline mock maker attaches an agent at run time, which the JDK now warns about and may block in future releases, so a JDK upgrade could stop mocks from working.

The fix: Configure mockito-core as a -javaagent for the test JVM in Maven or Gradle, following the javadoc section on explicit instrumentation setup.

Stubbing a spy with when()

Why it happens: when(spy.method()) calls the real method while setting up the stub, which can throw or cause side effects before the stub exists.

The fix: Use doReturn(value).when(spy).method() for spies, which registers the answer without invoking the real method.

Leaving a static mock open

Why it happens: MockedStatic stays active until closed. Created outside try-with-resources and never closed, it affects later tests on the same thread and fails with an error about an already registered static mock.

The fix: Always create static and construction mocks in try-with-resources, or close them in @AfterEach when the scope must span a test.

Varargs matchers that stopped matching after upgrading to 5

Why it happens: Mockito 5 changed how any() matches varargs: it now matches a single argument, so stubs written for older versions no longer match calls with several values or none.

The fix: Match any number of varargs with any(String[].class), using the array type of the varargs parameter, as the 5.0 release notes describe.

Compared

How Mockito compares

ToolLanguageWhat it fakesDefault strictnessStatic and final mockingLicence
MockitoJava; Kotlin via mockito-kotlinObjects inside the JVMLenient; strict stubs in JUnit extensionYes, inline mock maker by defaultMIT
MockKKotlinObjects inside the JVMStrict: unstubbed calls failYes, mockkStatic and mockkObjectApache 2.0
WireMockAny language, over HTTPHTTP services the code callsUnmatched requests return 404Not applicableApache 2.0

FAQ

Frequently asked questions

Is Mockito free?

Yes. Mockito is open source under the MIT licence, with no paid edition or feature tiers. Static and construction mocking, final class mocking, strict stubs and the JUnit extension are all included in the free artifacts on Maven Central. Companies that need a commercial support agreement can get one through a Tidelift subscription, which the project README links to.

Which Java version does Mockito 5 need?

Mockito 5 requires Java 11 or later. Projects still on Java 8 have to use Mockito 4. On Java 21 and later, Mockito 5 works but the inline mock maker should be configured as a Java agent in the build, because the JDK restricts agents loaded at run time and prints a warning when Mockito attaches itself.

Why does Mockito warn about a dynamically loaded agent?

Since Java 21 the JDK warns when a library attaches a Java agent to its own JVM, which is how Mockito’s default inline mock maker instruments classes. The Mockito javadoc recommends passing mockito-core as a -javaagent to the test JVM, with documented Maven and Gradle configurations. Future JDK releases may block dynamic loading entirely, so fixing the build is better than ignoring the warning.

Can Mockito mock static methods and final classes?

Yes. Since Mockito 5 the inline mock maker is the default, so final classes, final methods and enums can be mocked without configuration. Static methods are mocked with mockStatic and constructors with mockConstruction, both scoped to a try-with-resources block on the current thread. Version 5.24 added spyStatic for partially stubbing a class’s static methods.

Mockito vs MockK: which should I use for Kotlin?

For Kotlin-first code, MockK is usually the better fit: it is written for Kotlin, supports coroutines with coEvery, and mocks objects and top-level functions directly. Mockito works with Kotlin, especially through the mockito-kotlin helpers, and suits mixed Java and Kotlin codebases where one mocking library for both languages matters more than Kotlin idioms.

What is UnnecessaryStubbingException?

It is how Mockito’s strict stubs report a stub that the test never used. MockitoExtension enables strict stubs by default, so a when(...) the code under test does not trigger fails the test. Remove the stub or move it to the tests that need it. Where a shared stub is deliberate, mark it lenient(), or set @MockitoSettings(strictness = Strictness.LENIENT) on the class as a last resort.

Should I mock everything a class depends on?

No. Mock dependencies that are slow, non-deterministic or outside your control, such as external services, clocks and message brokers. Use real objects for simple domain classes and values, and in-memory fakes for repositories where practical. Tests that mock every collaborator and verify every call describe the implementation rather than the behaviour, and break whenever the code is refactored correctly.

Alternatives

Mockito alternatives worth weighing

Resources

Where to go next

Ready

Picking the wrong tool costs more than picking late.

QAble advises on toolchain decisions and then implements them, with engineers who have run these tools on real delivery. Start with a free QA audit.

Talk to QA Advisor