- 技術 💻
Mockitoの使い方|JUnit 5の書き方早見表とエラー別の直し方
Mockito は、テストの中で「本物の代わりに動く偽物(モック)」を作る Java のライブラリです。JUnit 5 では、テストクラスに @ExtendWith(MockitoExtension.class) を付け、依存先に @Mock、テスト対象に @InjectMocks を付けるのが基本の形です。
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock OrderRepository repository; // 偽物を作る
@Mock NotificationClient notification;
@InjectMocks OrderService service; // 偽物を注入したテスト対象
@Test
void 注文を1件取得できる() {
when(repository.findById(1L)).thenReturn(Optional.of(new Order(1L, "ノート", 2, "pending")));
assertEquals("ノート", service.get(1L).item());
}
}
この記事のテストコードは、Mockito 5.24.0(2026年9月23日リリース、GitHub の最新版)をソースからビルドし、JUnit Jupiter 5.10.1 で実行して、成功・失敗の出力をそのまま載せています。エラーは17件を、わざと起こして実際のメッセージで示しました。
Mockito の書き方早見表
| やりたいこと | 書き方 | 補足 |
|---|---|---|
| モックを作る | @Mock OrderRepository repository; |
mock(OrderRepository.class)、5.x では OrderRepository r = mock(); も可 |
| テスト対象にモックを入れる | @InjectMocks OrderService service; |
コンストラクタ → setter → フィールドの順に試す |
| 戻り値を決める(スタブ) | when(repo.findById(1L)).thenReturn(Optional.of(o)); |
決めていない呼び出しは既定値(後述) |
| 呼ぶたびに違う値を返す | thenReturn(3, 2, 0) |
最後の値が繰り返される |
| 引数から戻り値を作る | thenAnswer(inv -> ...inv.getArgument(0)...) |
採番の再現などに使う |
| 例外を投げさせる | when(...).thenThrow(new XxxException()) |
void は doThrow(...).when(mock).send(...) |
| 戻り値を決める(do 系) | doReturn(800).when(spy).unitPrice("ノート") |
spy と void で使う |
| 呼ばれたか確かめる | verify(repo).save(any()); |
times(2)・never()・atLeastOnce() |
| 何も呼ばれていない | verifyNoInteractions(repo, notification) |
余分な呼び出しは verifyNoMoreInteractions |
| 呼ばれた順番 | inOrder(a, b).verify(a).save(any()) |
順番が大事な処理に |
| 渡された引数を取り出す | @Captor ArgumentCaptor<Order> c; → verify(repo).save(c.capture()) |
c.getValue() で中身を確かめる |
| 本物を一部だけ差し替える | spy(new PriceCalculator()) + doReturn |
spy に when は使わない |
| static メソッドを差し替える | try (MockedStatic<X> m = mockStatic(X.class)) {...} |
ブロックの中だけ有効 |
| static を一部だけ差し替える | spyStatic(X.class) |
5.23.0 から |
new を差し替える |
try (MockedConstruction<X> m = mockConstruction(X.class)) {...} |
m.constructed() で作られた数 |
| enum を差し替える | try (MockedSingleton<Plan> m = mockSingleton(Plan.PRO)) {...} |
5.22.0 から |
| 余分なスタブを許す | lenient().when(...) |
strict stubs を1か所だけ緩める |
| Spring のテストで Bean を差し替える | @MockitoBean |
Spring Boot 4 で @MockBean は削除 |
確認環境:Mockito 5.24.0 と mockito-junit-jupiter 5.24.0(GitHub のタグ v5.24.0 のソースを javac でビルド)、Byte Buddy 1.15.11、Objenesis 3.6、JUnit Jupiter 5.10.1(JUnit Platform Console Launcher で実行)、OpenJDK 21.0.10 と 25.0.4.1(Ubuntu 24.04)。Mockito 5.24.0 のビルド定義(libs.versions.toml)での Byte Buddy は 1.17.7 です。Spring Boot の @MockitoBean と Maven・Gradle の設定は、Spring Boot 4.0 移行ガイドと Mockito の Javadoc で確かめました(確認日 2026年10月16日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Mockitoの使い方|JUnit 5の書き方早見表とエラー別の直し方」
Mockito 5 の変更点(4.x から上げるときに知っておくこと)
| 変更点 | 内容 | 影響 |
|---|---|---|
| 必要な Java | Java 11 以上(README) | Java 8 のプロジェクトは 4.x のまま |
| 既定のモックメーカー | inline mock maker が既定(5.0.0 から) | final クラス・final メソッド・record・enum も設定なしでモックにできる |
mockito-inline |
別に入れる必要がなくなった | 依存は mockito-core(+mockito-junit-jupiter) |
| Java 21 以降 | inline mock maker の自動アタッチで警告が出る | -javaagent で Mockito を指定する(後述) |
| strict stubs | MockitoExtension の既定は Strictness.STRICT_STUBS |
使わないスタブ・引数違いのスタブでテストが失敗する |
| 新しい API | mock() の型推論、mockStatic() の型推論(5.21.0)、mockSingleton(5.22.0)、spyStatic(5.23.0) |
後述 |
GitHub のリリースノートでは、5.24.0 で「-javaagent 使用時の static 初期化子の抑止(mockito.suppress.clinit)」「@Spy の mockMaker 指定」などが加わっています。
準備:依存関係
Spring Boot のプロジェクト
Spring Boot のテスト用スターター(spring-boot-starter-test)に JUnit・Mockito・AssertJ が含まれています。追加の依存は要りません。
Spring Boot を使わない場合(Maven)
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.24.0</version>
<scope>test</scope>
</dependency>
mockito-junit-jupiter を入れると、本体の mockito-core も一緒に入ります。
Gradle(Kotlin DSL)
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter:$junitVersion")
testImplementation("org.mockito:mockito-junit-jupiter:5.24.0")
}
JUnit 自体の書き方は、関連記事「JUnitの使い方|単体テストの書き方と観点【JUnit 5・6】」で扱っています。
Java 21 以降の警告と -javaagent の設定
Java 21 以降で Mockito 5 のテストを動かすと、最初のモックを作るときに次の出力が出ます(JDK 21.0.10 で実行)。
Mockito is currently self-attaching to enable the inline-mock-maker. This will no longer work in future releases of the JDK. Please add Mockito as an agent to your build as described in Mockito's documentation: https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html#0.3
WARNING: A Java agent has been loaded dynamically (/tmp/byteBuddyAgent16545567019408915997.jar)
WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning
WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information
WARNING: Dynamic loading of agents will be disallowed by default in a future release
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
inline mock maker は、Java エージェント(JVM に読み込ませてクラスを書き換える仕組み)を使います。Mockito は実行中に自分でエージェントを読み込みますが、Java 21 の JEP 451 で、この「動的な読み込み」に警告が出るようになりました。Mockito の Javadoc(0.3節)は、-javaagent で Mockito を明示的に指定する方法を案内しています。
| JVM オプション | Mockito の警告 | JVM の WARNING 4行 | テスト |
|---|---|---|---|
| なし | 出る | 出る | 15件成功 |
-XX:+EnableDynamicAgentLoading |
出ない | 出ない | 15件成功 |
-javaagent:mockito-core-5.24.0.jar |
出ない | 出ない | 15件成功 |
-XX:-EnableDynamicAgentLoading(読み込みを禁止) |
出る | ― | 失敗 |
最後の行は、将来の JDK で動的な読み込みが既定で禁止されたときの様子です。次のエラーでモックが作れなくなりました。
org.mockito.exceptions.base.MockitoInitializationException:
Could not initialize inline Byte Buddy mock maker.
It appears as if your JDK does not supply a working agent attachment mechanism.
Java : 21
...
Caused by: java.lang.IllegalStateException: Could not self-attach to current VM using external process - set a property net.bytebuddy.agent.attacher.dump to dump the process output to a file at the specified location
-XX:+EnableDynamicAgentLoading でも警告は消えますが、Javadoc には「標準化されたオプションではないため、将来の JDK で禁止される可能性がある」と書かれています。-javaagent の設定が長く使える方法です。
Maven(surefire)の設定
Mockito の Javadoc に載っている設定です。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<goals>
<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>
maven-dependency-plugin の properties ゴールで、${org.mockito:mockito-core:jar} が jar のパスに置き換わります。Javadoc には、@{argLine} が置き換えの時点で存在しないと文字どおり渡されて VM が落ち、The forked VM terminated without properly saying goodbye. VM crash or System.exit called. になる、とも書かれています。その場合は POM に空の <argLine/> プロパティを足します。
Gradle(Kotlin DSL)の設定
val mockitoAgent = configurations.create("mockitoAgent")
dependencies {
testImplementation(libs.mockito)
mockitoAgent(libs.mockito) { isTransitive = false }
}
abstract class MockitoAgentProvider : CommandLineArgumentProvider {
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val agentJar: ConfigurableFileCollection
override fun asArguments(): Iterable<String> =
listOf("-javaagent:${agentJar.asPath}")
}
tasks {
test {
jvmArgumentProviders.add(objects.newInstance<MockitoAgentProvider>().apply {
agentJar.from(mockitoAgent)
})
}
}
5.24.0 の Javadoc の例です(libs.mockito はバージョンカタログで org.mockito:mockito-core を指す名前)。CommandLineArgumentProvider を使うのは、Gradle のビルドキャッシュでパスを移動可能にするためと説明されています。
JDK 25 で「Java 25 (69) is not supported」が出たら
Byte Buddy(Mockito がクラスを作るのに使うライブラリ)の版が JDK に追いついていないと、JDK 25 でモックを作るときに失敗します。Byte Buddy 1.15.11 と組み合わせて JDK 25.0.4.1 で動かすと、15件中5件が次のエラーになりました。
Caused by: org.mockito.exceptions.base.MockitoException: Could not modify all classes [class PriceCalculator, class java.lang.Object]
Caused by: java.lang.IllegalArgumentException: Java 25 (69) is not supported by the current version of Byte Buddy which officially supports Java 24 (68) - update Byte Buddy or set net.bytebuddy.experimental as a VM property
直し方は、Mockito(と Byte Buddy)を新しい版に上げることです。 Spring Boot の依存管理で Mockito や Byte Buddy の版が固定されている場合は、byte-buddy.version などのプロパティで上げられます。-Dnet.bytebuddy.experimental=true を付けると、今度は Unsupported class file major version 68 で JDK のクラス(java.util.List など)の書き換えに失敗しました。
テストの対象にするコード
この記事では、次の注文サービスをテストします。OrderService が、保存(OrderRepository)と通知(NotificationClient)に依存している形です。
public record Order(Long id, String item, int quantity, String status) {}
public interface OrderRepository {
Optional<Order> findById(long id);
Order save(Order order);
int countByStatus(String status);
}
public interface NotificationClient {
void send(String to, String message);
}
public class OrderNotFoundException extends RuntimeException {
public OrderNotFoundException(long id) { super("注文が見つかりません: id=" + id); }
}
public class OrderService {
private final OrderRepository repository;
private final NotificationClient notification;
public OrderService(OrderRepository repository, NotificationClient notification) {
this.repository = repository;
this.notification = notification;
}
public Order get(long id) {
return repository.findById(id).orElseThrow(() -> new OrderNotFoundException(id));
}
public Order create(String item, int quantity) {
if (quantity < 1) throw new IllegalArgumentException("数量は1以上です: " + quantity);
Order saved = repository.save(new Order(null, item, quantity, "pending"));
notification.send("orders@example.com", "注文を受け付けました: id=" + saved.id());
return saved;
}
public String newOrderNumber() { return IdGenerator.next(); }
public int total(String item, int quantity) {
return new PriceCalculator().total(item, quantity);
}
}
public final class IdGenerator {
private IdGenerator() {}
public static String next() { return "ORD-" + UUID.randomUUID(); }
}
public class PriceCalculator {
public int unitPrice(String item) { return 1000; }
public int total(String item, int quantity) { return unitPrice(item) * quantity; }
}
本物の Repository はデータベースに、本物の NotificationClient はメールサービスにつながる想定です。この2つをモックにすると、データベースもメールサービスもなしで、OrderService の判断だけをテストできます。
JUnit 5 と Mockito の基本の形
次のテストクラスは、そのまま実行して15件すべて成功しました(JDK 21.0.10)。以降の節で1つずつ説明します。
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import java.util.*;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.*;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock OrderRepository repository;
@Mock NotificationClient notification;
@InjectMocks OrderService service;
@Captor ArgumentCaptor<Order> orderCaptor;
@Test
void 注文を1件取得できる() {
when(repository.findById(1L)).thenReturn(Optional.of(new Order(1L, "ノート", 2, "pending")));
Order result = service.get(1L);
assertEquals("ノート", result.item());
}
@Test
void 見つからなければ例外() {
// findById をスタブしない → 既定値の Optional.empty() が返る
OrderNotFoundException e = assertThrows(OrderNotFoundException.class, () -> service.get(99L));
assertEquals("注文が見つかりません: id=99", e.getMessage());
}
@Test
void 注文を作ると保存と通知が1回ずつ行われる() {
when(repository.save(any(Order.class))).thenAnswer(inv -> {
Order o = inv.getArgument(0);
return new Order(100L, o.item(), o.quantity(), o.status());
});
service.create("ノート", 3);
verify(repository).save(orderCaptor.capture());
Order saved = orderCaptor.getValue();
assertNull(saved.id());
assertEquals("pending", saved.status());
assertEquals(3, saved.quantity());
verify(notification).send(eq("orders@example.com"), eq("注文を受け付けました: id=100"));
verifyNoMoreInteractions(repository, notification);
}
@Test
void 保存に失敗したら通知しない() {
when(repository.save(any())).thenThrow(new IllegalStateException("DB接続エラー"));
IllegalStateException e = assertThrows(IllegalStateException.class, () -> service.create("ノート", 1));
assertEquals("DB接続エラー", e.getMessage());
verify(notification, never()).send(anyString(), anyString());
}
@Test
void 通知に失敗したら例外が伝わる() {
when(repository.save(any())).thenReturn(new Order(100L, "ノート", 1, "pending"));
doThrow(new RuntimeException("メール送信に失敗")).when(notification).send(anyString(), anyString());
RuntimeException e = assertThrows(RuntimeException.class, () -> service.create("ノート", 1));
assertEquals("メール送信に失敗", e.getMessage());
}
@Test
void 数量0なら何も呼ばない() {
assertThrows(IllegalArgumentException.class, () -> service.create("ノート", 0));
verifyNoInteractions(repository, notification);
}
@Test
void 連続した戻り値() {
when(repository.countByStatus("pending")).thenReturn(3, 2, 0);
assertEquals(List.of(3, 2, 0, 0),
List.of(repository.countByStatus("pending"), repository.countByStatus("pending"),
repository.countByStatus("pending"), repository.countByStatus("pending")));
}
@Test
void inOrderで順番を確かめる() {
when(repository.save(any())).thenReturn(new Order(1L, "ノート", 1, "pending"));
service.create("ノート", 1);
InOrder inOrder = inOrder(repository, notification);
inOrder.verify(repository).save(any());
inOrder.verify(notification).send(anyString(), anyString());
}
@Test
void lenientなら使わなくても失敗しない() {
lenient().when(repository.countByStatus("done")).thenReturn(5);
assertEquals(0, repository.countByStatus("pending"));
}
// spy・static・コンストラクタ・final クラスのテストは後の節に載せています
}
Test run finished after 2593 ms
[ 15 tests found ]
[ 15 tests successful ]
[ 0 tests failed ]
| 部品 | 役割 |
|---|---|
@ExtendWith(MockitoExtension.class) |
テストごとにモックを作り直し、テストの後に使い方の誤りを調べる |
@Mock |
そのフィールドにモックを作って入れる |
@InjectMocks |
テスト対象を作り、@Mock のモックを注入する |
@Captor |
ArgumentCaptor を作る(型引数つきでも警告が出ない) |
@InjectMocks は、コンストラクタ、setter、フィールドの順に注入を試します。 Javadoc にあるとおり、注入に失敗してもエラーにはならず、フィールドが null のまま残ることがあります(後述のエラー例)。
スタブしていない呼び出しの既定値
スタブ(「この引数で呼ばれたらこの値を返す」という取り決め)をしていない呼び出しは、次の値を返しました。
OrderRepository m = mock(OrderRepository.class);
System.out.println("[既定値] findById=" + m.findById(1L) + ", save=" + m.save(null) + ", countByStatus=" + m.countByStatus("x"));
List<String> list = mock(List.class);
System.out.println("[既定値] List.size=" + list.size() + ", List.isEmpty=" + list.isEmpty() + ", List.stream().count=" + list.stream().count());
System.out.println("[mock の toString] " + m);
[既定値] findById=Optional.empty, save=null, countByStatus=0
[既定値] List.size=0, List.isEmpty=false, List.stream().count=0
[mock の toString] Mock for OrderRepository, hashCode: 794563378
| 戻り値の型 | 既定値 |
|---|---|
オブジェクト(Order など) |
null |
数値(int・long など) |
0 |
boolean |
false |
Optional・Stream |
空 |
コレクション(List など) |
空のコレクション |
Future・CompletionStage |
完了済み(5.21.0 から) |
モックの List で size() は 0 なのに isEmpty() は false です。 boolean の既定値は常に false のためです。Javadoc も「List はモックにせず本物を使う」と書いています。
スタブ:when / thenReturn と do 系
引数マッチャー
any(Order.class)・anyString()・eq("...") を引数マッチャーと呼びます。
| マッチャー | 意味 |
|---|---|
any() / any(Order.class) |
何でも(any(Class) は型も確かめる) |
anyString()・anyLong()・anyInt() |
その型なら何でも(null は含まない) |
eq(値) |
equals で等しい |
isNull()・notNull() |
null か、null でないか |
argThat(o -> o.quantity() > 0) |
条件を自分で書く |
contains("id=")・startsWith("ORD-") |
文字列の一部 |
マッチャーを1つでも使ったら、すべての引数をマッチャーにします。 生の値は eq() で包みます。また、int などの基本型の引数に any() を使うと NullPointerException になります(anyInt() を使う)。どちらも後半のエラー例に出力を載せました。
when と doReturn の使い分け
| 場面 | 書き方 | 理由 |
|---|---|---|
| ふつうのモック | when(mock.m()).thenReturn(x) |
読みやすい。型をコンパイラが確かめる |
| 戻り値が void | doThrow(e).when(mock).m() |
when(void) はコンパイルエラー |
| spy | doReturn(x).when(spy).m() |
when(spy.m()) は本物の m() が動く |
| 同じメソッドを上書きする | doReturn(x).when(mock).m() |
when だと前のスタブが一度呼ばれる |
doReturn は型をコンパイラが確かめません。int を返すメソッドに doReturn("3") と書くと、実行時に WrongTypeOfReturnValue になります(後述)。
verify と ArgumentCaptor
verify(repository).save(orderCaptor.capture());
Order saved = orderCaptor.getValue();
assertNull(saved.id());
assertEquals("pending", saved.status());
| 書き方 | 意味 |
|---|---|
verify(mock).m() |
ちょうど1回(times(1) と同じ) |
verify(mock, times(2)).m() |
ちょうど2回 |
verify(mock, never()).m() |
1回も呼ばれていない |
verify(mock, atLeastOnce()).m() |
1回以上 |
verify(mock, atMost(3)).m() |
3回以下 |
verifyNoInteractions(mock) |
そのモックには何も呼ばれていない |
verifyNoMoreInteractions(mock) |
verify した以外の呼び出しがない |
inOrder(a, b).verify(a).m() |
呼ばれた順番 |
verify は、メール送信のように「戻り値がなく、呼ばれたこと自体が大事な処理」に使います。 戻り値で確かめられることは、戻り値をアサーションで確かめるほうが、実装の細部に縛られないテストになります。
ArgumentCaptor は、テスト対象の中で作られた値(この例では create の中の new Order(...))を取り出して確かめる仕組みです。複数回呼ばれた場合は getAllValues() で全部取り出せます。Javadoc では、ArgumentCaptor は verify と組み合わせ、スタブ(when)では any() や argThat() を使うことが勧められています。
spy:本物を一部だけ差し替える
spy は本物のオブジェクトを包み、指定したメソッドだけ差し替えるものです。 差し替えていないメソッドは本物が動きます。
@Test
void spyはdoReturnで差し替える() {
PriceCalculator spy = spy(new PriceCalculator());
doReturn(800).when(spy).unitPrice("ノート");
assertEquals(2400, spy.total("ノート", 3)); // total は本物、unitPrice だけ差し替え
verify(spy).unitPrice("ノート");
}
@Test
void spyにwhenを使うと本物が呼ばれる() {
List<String> spy = spy(new ArrayList<String>());
IndexOutOfBoundsException e = assertThrows(IndexOutOfBoundsException.class,
() -> when(spy.get(0)).thenReturn("foo"));
System.out.println("[spy+when] " + e);
doReturn("foo").when(spy).get(0);
assertEquals("foo", spy.get(0));
}
[spy+when] java.lang.IndexOutOfBoundsException: Index 0 out of bounds for length 0
when(spy.get(0)) と書くと、when に渡す前に本物の get(0) が実行され、空のリストなので例外になりました。spy には doReturn を使います。 Javadoc でも spy は「慎重に、たまに使うもの」とされています。spy で一部を差し替えたくなったら、クラスを分けて依存先をモックにできないかも考えます。
static メソッド・コンストラクタ・final クラスのモック
@Test
void staticメソッドのモック() {
try (MockedStatic<IdGenerator> mocked = mockStatic(IdGenerator.class)) {
mocked.when(IdGenerator::next).thenReturn("ORD-0001");
assertEquals("ORD-0001", service.newOrderNumber());
mocked.verify(IdGenerator::next);
}
System.out.println("[mockStatic の外] " + service.newOrderNumber().substring(0, 4) + "...");
}
@Test
void コンストラクタのモック() {
try (MockedConstruction<PriceCalculator> mocked = mockConstruction(PriceCalculator.class,
(mock, context) -> when(mock.total(anyString(), anyInt())).thenReturn(1))) {
assertEquals(1, service.total("ノート", 3));
assertEquals(1, mocked.constructed().size());
}
assertEquals(3000, service.total("ノート", 3)); // ブロックの外では本物
}
@Test
void finalクラスとrecordもモックにできる() {
PriceCalculator calc = mock(PriceCalculator.class);
when(calc.unitPrice("ノート")).thenReturn(500);
assertEquals(500, calc.unitPrice("ノート"));
Order order = mock(Order.class); // record(final クラス)
when(order.item()).thenReturn("モック");
assertEquals("モック", order.item());
}
[mockStatic の外] ORD-...
static とコンストラクタのモックは、try-with-resources のブロックの中だけ有効で、ブロックを出ると本物に戻りました。close() しないまま同じクラスをもう一度 mockStatic すると、static mocking is already registered in the current thread で失敗します(後述)。record の Order もモックにできましたが、値を運ぶだけの record は new で作るほうが読みやすいテストになります。
5.21〜5.23 で加わった書き方
class NewApiTest {
@Test
void 型引数から推論するmock() {
OrderRepository repo = mock(); // mock(OrderRepository.class) と同じ
assertTrue(mockingDetails(repo).isMock());
}
@Test
void 型引数から推論するmockStatic() { // 5.21.0
try (MockedStatic<IdGenerator> mocked = mockStatic()) {
mocked.when(IdGenerator::next).thenReturn("ORD-0001");
assertEquals("ORD-0001", IdGenerator.next());
}
}
@Test
void staticのspy() { // 5.23.0
try (MockedStatic<IdGenerator> spied = spyStatic(IdGenerator.class)) {
System.out.println("[spyStatic 本物] " + IdGenerator.next().substring(0, 4) + "...");
spied.when(IdGenerator::next).thenReturn("ORD-0001");
System.out.println("[spyStatic スタブ] " + IdGenerator.next());
}
}
@Test
void enumをmockSingleton() { // 5.22.0
try (MockedSingleton<Plan> mocked = mockSingleton(Plan.PRO)) {
when(Plan.PRO.monthlyFee()).thenReturn(0);
assertEquals(0, Plan.PRO.monthlyFee());
}
assertEquals(980, Plan.PRO.monthlyFee());
}
}
[spyStatic 本物] ORD-...
[spyStatic スタブ] ORD-0001
4件とも成功しました。mockSingleton は、enum の定数のように自分で作れない1つのインスタンスを、今のスレッドの中だけモックとして振る舞わせる API です。
static のモックに頼る前に、設計で避けられないかも考えます。 現在時刻なら Clock、採番ならインターフェースをコンストラクタで受け取る形にすると、static のモックなしでテストできます。
strict stubs と lenient(不要なスタブのエラー)
MockitoExtension は、既定で Strictness.STRICT_STUBS(厳格なスタブ)で動きます(MockitoSettings の既定値)。次の2つをテストの失敗として知らせます。
| 状況 | 例外 | いつ出るか |
|---|---|---|
| 一度も使われなかったスタブがある | UnnecessaryStubbingException |
テストの後(afterEach) |
| スタブと違う引数で呼ばれた | PotentialStubbingProblem |
呼ばれたその場で |
直す前:@BeforeEach の共通スタブ
@ExtendWith(MockitoExtension.class)
class E1UnnecessaryTest {
@Mock OrderRepository repository;
@Mock NotificationClient notification;
@InjectMocks OrderService service;
@BeforeEach
void setUp() {
when(repository.findById(1L)).thenReturn(Optional.of(new Order(1L, "ノート", 2, "pending")));
}
@Test
void 数量0なら例外() { // findById を使わないテスト
assertThrows(IllegalArgumentException.class, () -> service.create("ノート", 0));
}
}
org.mockito.exceptions.misusing.UnnecessaryStubbingException:
Unnecessary stubbings detected.
Clean & maintainable test code requires zero unnecessary code.
Following stubbings are unnecessary (click to navigate to relevant line of code):
1. -> at E1UnnecessaryTest.setUp(E1UnnecessaryTest.java:17)
Please remove unnecessary stubbings or use 'lenient' strictness. More info: javadoc for UnnecessaryStubbingException class.
テスト本体の assert は通っていても、テストは失敗になります。
直し方(優先順)
| 直し方 | 書き方 | 向いている場面 |
|---|---|---|
| 1. スタブを消す・使うテストに移す | @BeforeEach から各テストへ |
ほとんどの場合 |
| 2. そのスタブだけ緩める | lenient().when(...) |
共通の準備で、使わないテストがあると分かっている |
| 3. そのモックだけ緩める | @Mock(strictness = Mock.Strictness.LENIENT) |
設定用のモックなど |
| 4. クラス全体を緩める | @MockitoSettings(strictness = Strictness.LENIENT) |
移行中の一時的な対応 |
使われないスタブは、読む人に「この呼び出しが関係している」と誤解させます。 まず消すか、使うテストに移します。lenient() は範囲を絞って使います。この記事の lenientなら使わなくても失敗しない() は、lenient() を付けたスタブを使わなくても成功しました。
PotentialStubbingProblem:引数が違う
@Test
void 引数が違う() {
when(repository.findById(1L)).thenReturn(Optional.of(new Order(1L, "ノート", 2, "pending")));
service.get(2L);
}
org.mockito.exceptions.misusing.PotentialStubbingProblem:
Strict stubbing argument mismatch. Please check:
- this invocation of 'findById' method:
repository.findById(2L);
-> at OrderService.get(OrderService.java:11)
- has following stubbing(s) with different arguments:
1. repository.findById(1L); stubbed with: [Returns: Optional[Order[id=1, item=ノート, quantity=2, status=pending]]]
-> at E2PotentialTest.引数が違う(E2PotentialTest.java:17)
Typically, stubbing argument mismatch indicates user mistake when writing tests.
Mockito fails early so that you can debug potential problem easily.
However, there are legit scenarios when this exception generates false negative signal:
- stubbing the same method multiple times using 'given().will()' or 'when().then()' API
Please use 'will().given()' or 'doReturn().when()' API for stubbing.
- stubbed method is intentionally invoked with different arguments by code under test
Please use default or 'silent' JUnit Rule (equivalent of Strictness.LENIENT).
For more information see javadoc for PotentialStubbingProblem class.
スタブは findById(1L)、実際の呼び出しは findById(2L) だと、どちらの行かまで示されます。テストの値の誤りか、テスト対象のバグかを確かめます。わざと違う引数でも呼ぶ処理なら、そのスタブを lenient() にします。
よくあるエラーと直し方(17件の実際の出力)
| No. | エラー | よくある原因 | 直し方 |
|---|---|---|---|
| 1 | UnnecessaryStubbingException |
使われないスタブ | スタブを消す・移す。必要なら lenient() |
| 2 | PotentialStubbingProblem |
スタブと違う引数で呼ばれた | 引数の値を確かめる |
| 3 | UnfinishedStubbingException |
thenReturn の書き忘れ |
when(...) を1行で完結させる |
| 4 | UnfinishedStubbingException |
thenReturn(...) の中で別のスタブ |
スタブの値は先に変数へ |
| 5 | WrongTypeOfReturnValue |
doReturn の型違い |
戻り値の型に合わせる |
| 6 | InvalidUseOfMatchersException |
マッチャーと生の値の混在 | 生の値を eq() で包む |
| 7 | NullPointerException(any()) |
int の引数に any() |
anyInt() を使う |
| 8 | NullPointerException(モックが null) |
@ExtendWith の付け忘れ |
@ExtendWith(MockitoExtension.class) |
| 9 | NullPointerException(テスト対象の中) |
@InjectMocks に足りない @Mock |
依存の数だけ @Mock を書く |
| 10 | MissingMethodInvocationException |
モックでないものを when |
@Mock か mock() で作る |
| 11 | NotAMockException |
モックでないものを verify |
同上 |
| 12 | WantedButNotInvoked |
期待した呼び出しが無い | 分岐の条件を確かめる |
| 13 | ArgumentsAreDifferent |
呼ばれたが引数が違う | 差分(position)を見る |
| 14 | TooManyActualInvocations |
回数が違う | times(n) を合わせる |
| 15 | static mocking is already registered |
mockStatic の閉じ忘れ |
try-with-resources で書く |
| 16 | 'void' type not allowed here |
void を when に渡した |
doThrow().when(mock).m() |
| 17 | Java 25 (69) is not supported |
Byte Buddy が JDK 25 に未対応の版 | Mockito・Byte Buddy を上げる |
1・2・17 は上の節に出力を載せました。残りを順に載せます。
3・4. UnfinishedStubbingException
@Test
void thenReturnの書き忘れ() {
when(repository.countByStatus("pending"));
repository.countByStatus("done");
}
@Test
void thenReturnの中で別のスタブ() {
when(repository.countByStatus("pending")).thenReturn(stubbedPrice());
}
int stubbedPrice() {
when(calc.unitPrice("ノート")).thenReturn(500); // 別のモックのスタブ
return 500;
}
org.mockito.exceptions.misusing.UnfinishedStubbingException:
Unfinished stubbing detected here:
-> at E3UnfinishedTest.thenReturnの書き忘れ(E3UnfinishedTest.java:23)
E.g. thenReturn() may be missing.
Examples of correct stubbing:
when(mock.isOk()).thenReturn(true);
when(mock.isOk()).thenThrow(exception);
doThrow(exception).when(mock).someVoidMethod();
Hints:
1. missing thenReturn()
2. you are trying to stub a final method, which is not supported
3. you are stubbing the behaviour of another mock inside before 'thenReturn' instruction is completed, possibly on another thread
2件とも同じ例外です。2件目は、thenReturn(...) の引数を作る途中で別のスタブが始まったため、1つ目のスタブが完了していないと判断されました。スタブに渡す値は、先に変数に入れてから thenReturn(変数) に渡します。
5. WrongTypeOfReturnValue
doReturn("3").when(repository).countByStatus("pending"); // countByStatus は int を返す
org.mockito.exceptions.misusing.WrongTypeOfReturnValue:
String cannot be returned by countByStatus()
countByStatus() should return int
***
If you're unsure why you're getting above error read on.
Due to the nature of the syntax above problem might occur because:
1. This exception *might* occur in wrongly written multi-threaded tests.
Please refer to Mockito FAQ on limitations of concurrency testing.
2. A spy is stubbed using when(spy.foo()).then() syntax. It is safer to stub spies -
- with doReturn|Throw() family of methods. More in javadocs for Mockito.spy() method.
doReturn は型をコンパイラが確かめないため、実行時に分かります。メッセージにあるとおり、spy に when を使ったときや、複数スレッドでスタブしたときにも出ます。
6. InvalidUseOfMatchersException
notification.send("a@example.com", "hello");
verify(notification).send(anyString(), "hello"); // マッチャーと生の値が混在
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Invalid use of argument matchers!
2 matchers expected, 1 recorded:
-> at E5MatchersTest.マッチャーと生の値の混在(E5MatchersTest.java:16)
This exception may occur if matchers are combined with raw values:
//incorrect:
someMethod(any(), "raw String");
When using matchers, all arguments have to be provided by matchers.
For example:
//correct:
someMethod(any(), eq("String by matcher"));
verify(notification).send(anyString(), eq("hello")); に直します。
7. NullPointerException:int の引数に any()
when(calc.total(anyString(), any())).thenReturn(1); // total(String, int)
java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because the return value of "org.mockito.Mockito.any()" is null
any() は null を返すため、int に変換するところで落ちます。基本型の引数には anyInt()・anyLong()・anyBoolean() を使います。 この NPE の後、同じテストクラスの次のテストが Misplaced or misused argument matcher detected here で失敗しました。片付けられなかったマッチャーが残るためで、最初に失敗したテストから直すと原因にたどり着けます。
8. NullPointerException:@ExtendWith の付け忘れ
class E6NpeTest { // @ExtendWith(MockitoExtension.class) を付け忘れた
@Mock OrderRepository repository;
@InjectMocks OrderService service;
@Test
void モックがnull() {
when(repository.countByStatus("pending")).thenReturn(1);
}
}
java.lang.NullPointerException: Cannot invoke "OrderRepository.countByStatus(String)" because "this.repository" is null
@Mock は、MockitoExtension(か MockitoAnnotations.openMocks(this))が処理して初めてモックが入ります。
9. NullPointerException:@InjectMocks に足りない @Mock
@ExtendWith(MockitoExtension.class)
class E11InjectTest {
@Mock OrderRepository repository; // NotificationClient の @Mock を書き忘れた
@InjectMocks OrderService service;
@Test
void 通知でNPE() {
when(repository.save(any())).thenReturn(new Order(1L, "ノート", 1, "pending"));
service.create("ノート", 1);
}
}
java.lang.NullPointerException: Cannot invoke "NotificationClient.send(String, String)" because "this.notification" is null
OrderService.create(OrderService.java:17)
コンストラクタの2つ目の引数に null が渡され、テスト対象の中で NPE になりました。@InjectMocks は足りない依存があってもエラーにしません。 テスト対象の中の NPE は、まず @Mock の数と型を確かめます。
10・11. MissingMethodInvocationException / NotAMockException
PriceCalculator real = new PriceCalculator();
when(real.unitPrice("ノート")).thenReturn(500); // 10
verify(real).unitPrice("ノート"); // 11
org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.
For example:
when(mock.getArticles()).thenReturn(articles);
Also, this error might show up because:
1. you stub either of: final/private/native/equals()/hashCode() methods.
Those methods *cannot* be stubbed/verified.
Mocking methods declared on non-public parent classes is not supported.
2. inside when() you don't call method on mock but on some other object.
org.mockito.exceptions.misusing.NotAMockException:
Argument passed to verify() is of type PriceCalculator and is not a mock!
Make sure you place the parenthesis correctly!
See the examples of correct verifications:
verify(mock).someMethod();
verify(mock, times(10)).someMethod();
verify(mock, atLeastOnce()).someMethod();
verify(mock.someMethod()) のように括弧の位置を間違えた場合も、NotAMockException になります。
12〜14. verify の失敗(呼ばれていない・引数が違う・回数が違う)
@Test
void 呼ばれていない() {
verify(notification).send(anyString(), anyString());
}
@Test
void 引数が違う() {
notification.send("orders@example.com", "注文を受け付けました: id=100");
verify(notification).send("orders@example.com", "注文を受け付けました: id=101");
}
@Test
void 回数が違う() {
notification.send("a", "b");
notification.send("a", "b");
verify(notification).send("a", "b");
}
Wanted but not invoked:
notification.send(
<any string>,
<any string>
);
-> at E8WantedTest.呼ばれていない(E8WantedTest.java:14)
Actually, there were zero interactions with this mock.
Argument(s) are different! Wanted:
notification.send(
"orders@example.com",
"注文を受け付けました: id=101"
);
-> at E8WantedTest.引数が違う(E8WantedTest.java:20)
Actual invocations have different arguments at position [1]:
notification.send(
"orders@example.com",
"注文を受け付けました: id=100"
);
org.mockito.exceptions.verification.TooManyActualInvocations:
notification.send("a", "b");
Wanted 1 time:
-> at E8WantedTest.回数が違う(E8WantedTest.java:27)
But was 2 times:
-> at E8WantedTest.回数が違う(E8WantedTest.java:25)
-> at E8WantedTest.回数が違う(E8WantedTest.java:26)
引数違いでは at position [1](0から数えて2番目の引数)と、違う引数の場所まで示されます。verify(mock) は「ちょうど1回」なので、2回呼ばれると失敗します。2回なら times(2)、1回以上なら atLeastOnce() と書きます。
15. static モックの閉じ忘れ
MockedStatic<IdGenerator> m1 = mockStatic(IdGenerator.class);
MockedStatic<IdGenerator> m2 = mockStatic(IdGenerator.class);
org.mockito.exceptions.base.MockitoException:
For IdGenerator, static mocking is already registered in the current thread
To create a new mock, the existing static mock registration must be deregistered
MockedStatic は close() するまで有効です。try-with-resources で書くと、閉じ忘れが起きません。 @BeforeEach で作る場合は @AfterEach で close() します。
16. void のメソッドを when に渡した(コンパイルエラー)
when(n.send("a", "b")).thenThrow(new RuntimeException());
E9VoidWhenTest.java:8: error: 'void' type not allowed here
when(n.send("a", "b")).thenThrow(new RuntimeException());
^
1 error
doThrow(new RuntimeException()).when(n).send("a", "b"); と、do 系から書きます。
Spring Boot 4 での @MockitoBean
Spring のテストで、Spring が管理する Bean をモックに差し替えるには @MockitoBean を使います。
@WebMvcTest(OrderController.class)
class OrderControllerTest {
@Autowired
MockMvc mockMvc;
@MockitoBean
OrderService orderService;
@Test
void 存在しない注文は404() throws Exception {
// OrderNotFoundException を404に変換する @RestControllerAdvice がある前提
when(orderService.get(99L)).thenThrow(new OrderNotFoundException(99L));
mockMvc.perform(get("/api/orders/99"))
.andExpect(status().isNotFound());
}
}
| アノテーション | 提供元 | 使う場面 |
|---|---|---|
@Mock |
Mockito | Spring を起動しない単体テスト(速い) |
@MockitoBean |
Spring Framework(6.2 以降) | Spring のテストで Bean を差し替える |
@MockitoSpyBean |
Spring Framework(6.2 以降) | Bean を spy で包む |
@MockBean / @SpyBean |
Spring Boot | 3.4 で非推奨、Spring Boot 4 で削除 |
Spring Boot 4.0 の移行ガイドには、次のように書かれています。
@MockBeanと@SpyBeanのサポートは削除され、@MockitoBean・@MockitoSpyBeanに置き換わった(パッケージはorg.springframework.test.context.bean.override.mockito)。- 新しいアノテーションは、テストクラスのフィールドには書けるが、
@Configurationクラスの中には書けない。 - 3.4 で非推奨になった
MockitoTestExecutionListenerは削除された。@Mockや@Captorが期待どおり動かないときは、Mockito 自身のMockitoExtensionを使う。
Service のロジックだけを確かめたいなら、Spring を起動せず @Mock で書くほうが速く、失敗の原因も追いやすくなります。 Controller のテストの書き分けは関連記事「Spring Bootテスト入門|@WebMvcTestとMockMvcの使い分け」で扱っています。Spring Boot 4 への移行全体は関連記事「Spring Boot 4移行の手順|3から4の変更点一覧とチェックリスト」で扱っています。
モックにするもの・しないもの
何でもモックにすると、「モックに決めたとおりに動いた」ことしか確かめていないテストになります。
| 対象 | モックにするか | 理由 |
|---|---|---|
| 外部 API・メール送信 | する | 遅い・費用がかかる・結果が毎回変わる |
| DB アクセス(Service の単体テスト) | する | Service の判断だけを確かめたい |
| DB アクセス(Repository のテスト) | しない | SQL の正しさは本物の DB でしか確かめられない |
| 現在時刻・乱数 | Clock などを渡す設計にする |
static のモックより素直 |
| record・値オブジェクト・DTO | しない | new で作るほうが読みやすい |
List・String など JDK のクラス |
しない | 既定値が本物と違う(isEmpty() が false など) |
| テスト対象のクラス自身 | しない | spy で差し替えたくなったら設計を見直すサイン |
スタブが何個も並ぶ、リファクタリングだけでテストが大量に壊れる、モックがモックを返している、といった状態は、テスト対象の責務が多すぎるサインです。スタブ・モック・スパイ・フェイクの違いは、関連記事「テストダブルとは|スタブ・モック・スパイ・フェイクの違いと使い分け」で扱っています。HTTP の外部 API を本物に近い形で差し替える方法は、関連記事「WireMockの使い方|スタブ定義JSONとJava・Spring Boot例」で扱っています。Kotlin では MockK もよく使われ、Mockito との対応は関連記事「MockK 使い方|every・verify・coEveryとMockito対応表」で扱っています。
よくある質問
Q. Mockito とは何ですか? A. Java のテストで、依存先を偽物(モック)に差し替えるライブラリです。戻り値を決める(スタブ)、呼ばれたかを確かめる(verify)の2つが中心です。最新版は 5.24.0(2026年9月23日、GitHub のリリース)です。
Q. JUnit で Mockito を使うには?
A. mockito-junit-jupiter を依存に入れ、テストクラスに @ExtendWith(MockitoExtension.class) を付けます。依存先に @Mock、テスト対象に @InjectMocks を付けると、テストごとにモックが作られて注入されます。
Q. JUnit 5 で @RunWith(MockitoJUnitRunner.class) は使えますか?
A. @RunWith は JUnit 4 の仕組みです。JUnit 5(Jupiter)では @ExtendWith(MockitoExtension.class) を使います。
Q. @Mock と mock() はどちらを使いますか?
A. 機能は同じです。@Mock と MockitoExtension の組み合わせでは、strict stubs のチェックがテストごとに自動で働きます。1つのテストの中だけで使うなら mock(OrderRepository.class)(5.x では OrderRepository r = mock();)が手軽です。
Q. lenient とは何ですか?
A. strict stubs のチェックを、そのスタブ(lenient().when(...))や、そのモック(@Mock(strictness = Mock.Strictness.LENIENT))だけ緩める指定です。使わないスタブがあっても UnnecessaryStubbingException になりません。まずはスタブを消すか移すことを考え、緩めるのは範囲を絞ります。
Q. final クラスや final メソッドはモックにできますか?
A. Mockito 5 では inline mock maker が既定のため、設定なしでモックにできます。この記事でも final クラスの record(Order)をモックにできました。Java 21 以降は -javaagent の設定を入れると警告が出ません。
Q. Spring Boot 4 に上げたら @MockBean がコンパイルエラーになりました。
A. Spring Boot 4 で @MockBean と @SpyBean は削除されました。@MockitoBean・@MockitoSpyBean に置き換えます。@Configuration クラスの中で使っていた場合は、テストクラスのフィールドに移します。
Q. Mockito を使ったテストは並列実行できますか?
A. モックはテストごとに作られるため、テストメソッドの間で共有しなければ並列に動かせます。static のモックは「今のスレッドだけ」で有効です。Mockito の FAQ にある並行テストの制限(スタブを複数スレッドで行うと WrongTypeOfReturnValue などが出る)には気をつけます。不安定なテストの見つけ方は関連記事「フレーキーテストの原因と対策|不安定なテストの見つけ方と直し方」で扱っています。
まとめ
- 基本は
@ExtendWith(MockitoExtension.class)・@Mock・@InjectMocks。戻り値はwhen/thenReturn、void の例外はdoThrow、spy はdoReturn - 呼び出しの確認は verify、渡された値の中身は ArgumentCaptor
- Mockito 5 は inline mock maker が既定。final クラス・record・static・コンストラクタ・enum(
mockSingleton)も差し替えられる - Java 21 以降は
-javaagent:${org.mockito:mockito-core:jar}を設定すると、自動アタッチの警告が消える - strict stubs が既定。
UnnecessaryStubbingExceptionはスタブを消すか移して直し、lenient()は範囲を絞る intの引数にany()を使うと NPE。基本型はanyInt()- JDK 25 で
Java 25 (69) is not supportedが出たら、Mockito と Byte Buddy を上げる - Spring Boot 4 では
@MockBeanが削除。@MockitoBeanに置き換える
参考・出典
確認日はいずれも 2026年10月16日です。
- Mockito(GitHub、v5.24.0 のソースと README):https://github.com/mockito/mockito
- Mockito Releases(5.16.0〜5.24.0 のリリースノート):https://github.com/mockito/mockito/releases
- Mockito Javadoc「0.3. Explicitly setting up instrumentation for inline mocking (Java 21+)」:https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html#0.3
- Mockito ソース MockitoSettings.java(strictness の既定値 STRICT_STUBS):https://github.com/mockito/mockito/blob/v5.24.0/mockito-extensions/mockito-junit-jupiter/src/main/java/org/mockito/junit/jupiter/MockitoSettings.java
- JEP 451: Prepare to Disallow the Dynamic Loading of Agents:https://openjdk.org/jeps/451
- Byte Buddy(GitHub):https://github.com/raphw/byte-buddy
- Spring Boot 4.0 Migration Guide:https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-4.0-Migration-Guide
- Spring Framework Reference「@MockitoBean and @MockitoSpyBean」:https://docs.spring.io/spring-framework/reference/testing/annotations/integration-spring/annotation-mockitobean.html
- JUnit User Guide:https://docs.junit.org/current/user-guide/index.html
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Mockitoの使い方|JUnit 5の書き方早見表とエラー別の直し方」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。