モックとは、単体テストで依存オブジェクトの代わりに置き、その依存がどう呼ばれたか(回数・引数)を検証するテストダブルです。決まった値を返してテスト対象に入力を与えるだけのスタブとは、確かめる対象が違います。この記事では、ISTQB用語集の定義とテストダブル5分類でモックの位置を押さえたうえで、スタブ・ドライバ・スパイ・フェイクとの違いを整理し、同じ仕様をPython・Jest・Mockito・PHPUnit・RSpecの5つで書き分けたコードを載せます。コードはすべて2026年10月9日に手元で実行し、通ることを確認しました。
まとめ:モックとスタブの検証対象と使い分け
モックとスタブはどちらも依存を置き換えるテストダブルですが、入力を用意するか、呼び出しを検証するかで役割が異なります。判断に必要な点は次のとおりです。
- スタブは設定した戻り値(や例外)を返し、テスト対象に入力を与えます。確かめるのはテスト対象の戻り値や最終状態です(状態検証)。
- モックは「send が1回、この引数で呼ばれた」のような期待を持ち、満たされなければテストを失敗させます(振る舞い検証)。メール送信・決済・外部登録など、戻り値では見えない副作用を確かめるときに使います。
- ドライバはテストダブルではなく、テスト対象を呼び出す側の代用品です。下位モジュールを先に作るボトムアップの結合テストで使います。
- モックを増やしすぎるとテストが実装手順に密着し、振る舞いを変えないリファクタリングでも失敗します。Mockitoの厳格スタブやPHPUnit 12.5以降の通知は、使われないスタブや期待値の無いモックを機械的に指摘してくれます。
モックの定義と、テストダブル5分類の中での位置づけ
呼び名が揺れやすい領域なので、最初に定義の出どころを確認します。
ISTQB用語集によるテストダブル・スタブ・モックの定義
ソフトウェアテストの資格団体ISTQBの用語集(日本語版はJSTQBが翻訳)は、ISTQB Glossaryの日本語版で3つの語を次のように定義しています。
- テストダブル:テストを実施するためにテスト対象が呼び出す代替コンポーネント。
- スタブ:あらかじめ決められたレスポンスを提供する、テストダブルの一種。
- モック:テストダブルの一種。テスト中に、コンポーネントの期待される動作をシミュレートする。
用語集はスタブとモックを同列の「テストダブルの一種」として扱っており、「モック」をテストダブル全体の別名としては使っていません。
テストダブル5分類(ダミー・スタブ・スパイ・モック・フェイク)の境界
テストダブルという総称と5分類を定義したのは、Gerard Meszaros氏の『xUnit Test Patterns』(2007年)です。分類は同書のパターンサイトxUnit Patternsの「Test Double」で読めます。Martin Fowler氏は後述の記事でこの分類を紹介し、広めた側です。5つは、入力の提供、呼び出しの記録・検証、簡易実装という役割で区別できます。
| 種類 | 役割 | 戻り値 | 呼び出しの検証 |
|---|---|---|---|
| ダミー | 引数を埋めるだけで使われない | なし | しない |
| スタブ | 設定した応答で入力を与える | 設定値・例外 | しない |
| スパイ | 呼び出しを記録し、テストが後で照合 | 返せる | テスト側で照合 |
| モック | 期待する呼ばれ方を実行前に設定 | 返せる | モック側で照合 |
| フェイク | 簡易実装で本物のように動く | 実際に計算 | しない |
5分類それぞれの使い分けと総称としてのテストダブルはテストダブル(TestDouble)とは|5分類の違いと使い分けで詳しく扱っています。この記事は、実務で混同が多いモックとスタブの線引きと書き方に絞ります。
モックとスタブの違いと、ドライバ・スパイ・フェイクとの区別
スタブは状態検証、モックは振る舞い検証という判断軸
モックとスタブの差は、Martin Fowler氏の記事Mocks Aren’t Stubs(2007年1月2日改訂版)が示す2つの検証スタイルで説明できます。
スタブを使うのは状態検証(state verification)です。依存が返す値を固定し、その入力を受けたテスト対象の戻り値や状態をアサートします。割引計算なら、料金マスタから固定価格を返すスタブを置き、計算結果の金額だけを確かめます。スタブがどう呼ばれたかは問いません。
モックを使うのは振る舞い検証(behavior verification)です。どのメソッドが何回どの引数で呼ばれたかを検証します。通知の送信や外部システムへの登録のように、戻り値に現れない処理の依頼を検証するときに向きます。実際の配信や登録の成功は、実物を使う結合テストで別に確認します。Fowler氏は、状態検証を主に使う流儀を古典派(classical TDD)、振る舞い検証を多用する流儀をモック派(mockist TDD)と呼び分けました。同じ記事は、モック派のテストは実装の詳細に結び付きやすいとも書いています。
判断の基準は1つで足ります。その依存の呼ばれ方が、変えてはいけない仕様かどうかです。仕様ならモックで検証し、単なる入力源ならスタブを置きます。
スタブとドライバの違い:呼ばれる側と呼ぶ側の代用品
日本のテスト工程の解説では、スタブはドライバと対で出てきます。ISTQB用語集はドライバーを「一時的に別のコンポーネントを置き換え、テストアイテムを単独で制御または呼び出す、コンポーネントまたはツール」と定義しています。違いは呼び出しの向きです。
- スタブ:テスト対象から呼ばれる下位モジュールの代用品。上位から先に作るトップダウンの結合テストで、まだ無い下位モジュールの代わりに置きます。
- ドライバ:テスト対象を呼ぶ上位モジュールの代用品。下位から先に作るボトムアップの結合テストで、まだ無い呼び出し元の代わりに置きます。
xUnit系のフレームワークで書く単体テストでは、テストメソッドそのものがテスト対象を呼ぶドライバの役を果たします。この構成では独立したドライバを用意する代わりにテストメソッドから対象を呼び出し、呼ばれる依存をスタブやモックに置き換えます。テスト工程全体でのトップダウン・ボトムアップの位置づけはシステム開発のテスト工程とは?種類・流れとV字モデル・発注者が見る判断軸で整理しています。
スパイ・フェイクとの違いと、何でも「モック」と呼ばれる理由
現場で何でも「モック」と呼ばれるのは、主要なライブラリがスタブ・モック・スパイを1つのAPIで提供しているからです。Mockitoの mock() で作ったオブジェクトは、when().thenReturn() を書けばスタブとして、verify() を書けばモックとして働きます。呼び名ではなく、テストの中でそのオブジェクトに何をさせているかで区別します。
スパイもモックも呼ばれ方を見ますが、5分類での違いは期待をいつ、誰が持つかです。モックは期待を実行前に設定し、モック自身が照合します。スパイは呼び出しを記録するだけで、照合はテストが実行後に書きます。RSpecでは expect(...).to receive がモック、have_received がスパイです。この厳密な区別に従うと、Mockitoの verify やunittest.mockの assert_called_once_with は実行後に照合するスパイ型の書き方ですが、ライブラリも現場もこれをモックと呼んでいます。以降の表と実装例もこの広い呼び方に合わせます。
フェイクは性格がまったく異なり、インメモリDBのように本物に近いロジックを中に持ちます。複数回の読み書きが整合することを確かめたいリポジトリは、戻り値を積み上げたスタブよりフェイクのほうが実装手順に縛られません。AWSのAPIをローカルで受けるLocalStackもフェイクの一種で、どこまでを代用品で確かめるかの線引きはLocalStackとは?AWSをローカルで動かす手順と有料化後の料金・判断基準で扱っています。
同じ仕様を5言語のモックとスタブで書き分ける実装例
題材は「在庫がゼロなら運用担当へ通知を送る」関数に固定し、在庫リポジトリをスタブ、通知の送信をモックにします。どの言語でも手順は3つです。依存を差し替える、戻り値を決める、呼ばれ方を検証する。検証に使った版と、それぞれに当たるAPIの対応は次のとおりです。
| 環境(検証した版) | スタブ(戻り値) | 呼ばれ方の検証 | 本物を動かすスパイ |
|---|---|---|---|
| Python 3.14.7 unittest.mock | return_value |
assert_called_once_with |
wraps= |
| Jest 30.5.2 | mockReturnValue |
toHaveBeenCalledWith |
jest.spyOn |
| Mockito 5.24.0 + JUnit 6.1.3 | when().thenReturn() |
verify() |
spy() |
| PHPUnit 13.4.1 | createStub |
createMock + expects |
専用APIなし |
| RSpec 3.13(rspec-mocks 3.13.8) | allow().to receive |
expect().to receive |
and_call_original |
版は2026年10月9日時点のレジストリで確認しました。Mockitoは5.24.0が2026年9月23日、Jestは30.5.2が2026年9月18日の公開です。
Python unittest.mock:return_valueでスタブ、assert_called_once_withでモック
unittest.mockはPython 3.3で標準ライブラリに入ったため、追加インストールは要りません(Python公式ドキュメント)。以下を test_notify.py として保存し、同じディレクトリで python3 -m unittest test_notify を実行すると2件とも通ります。
import unittest
from unittest.mock import MagicMock
def notify_if_empty(stock_repo, mailer, sku):
if stock_repo.count(sku) == 0:
mailer.send("[email protected]", sku + " is out of stock")
class NotifyTest(unittest.TestCase):
def test_sends_once_when_empty(self):
repo = MagicMock()
repo.count.return_value = 0 # スタブ:入力を用意する
mailer = MagicMock() # モック:呼ばれ方を検証する
notify_if_empty(repo, mailer, "SKU-1")
mailer.send.assert_called_once_with("[email protected]", "SKU-1 is out of stock")
def test_does_not_send_when_in_stock(self):
repo = MagicMock()
repo.count.return_value = 3
mailer = MagicMock()
notify_if_empty(repo, mailer, "SKU-1")
mailer.send.assert_not_called()
if __name__ == "__main__":
unittest.main()
依存を引数で受け取らず関数の中で生成している既存コードは、@patch デコレータで、テスト対象が依存を参照する名前を差し替えます。service.py が from mailer import Mailer と取り込んでいるなら、定義元の mailer.Mailer ではなく service.Mailer を指定します(公式ドキュメントの「Where to patch」)。デコレータを重ねたときは下から順に引数へ渡る(公式ドキュメントの表現では bottom up)ため、引数の並びを逆に書くと別の依存を検証してしまいます。
Jest:jest.fnでモック関数、jest.spyOnでスパイ
jest.fn() はモック関数を作り、mockReturnValue で戻り値を決めればスタブとして、toHaveBeenCalledWith で照合すればモックとして働きます。jest.spyOn は本物の実装を動かしたまま呼び出しを記録するスパイです。作業ディレクトリで npm init -y と npm install --save-dev [email protected] を実行し、以下を notify.test.js として保存して npx jest を実行するとJest 30.5.2で2件とも通ります。
function notifyIfEmpty(stockRepo, mailer, sku) {
if (stockRepo.count(sku) === 0) {
mailer.send("[email protected]", sku + " is out of stock");
}
}
test("在庫ゼロなら通知を1回だけ送る", () => {
const repo = { count: jest.fn().mockReturnValue(0) };
const mailer = { send: jest.fn() };
notifyIfEmpty(repo, mailer, "SKU-1");
expect(mailer.send).toHaveBeenCalledTimes(1);
expect(mailer.send).toHaveBeenCalledWith("[email protected]", "SKU-1 is out of stock");
});
test("spyOnは本物を動かしたまま呼び出しを記録する", () => {
const mailer = { send: (to, subject) => to + ":" + subject };
const spy = jest.spyOn(mailer, "send");
notifyIfEmpty({ count: () => 0 }, mailer, "SKU-1");
expect(spy).toHaveReturnedWith("[email protected]:SKU-1 is out of stock");
spy.mockRestore();
});
呼び出しごとに戻り値を変えるときは mockReturnValueOnce を並べます。mockImplementation での実装差し替えや非同期関数の扱いはJest mockImplementationの使い方|戻り値・非同期・実装差し替えを実務目線で解説にまとめています。
Java Mockito:when/verifyとMockitoExtensionの厳格スタブ
Javaは when が状態の用意、verify が振る舞いの検証とAPI名に役割が出ます。Mavenプロジェクトの src/main/java/demo にテスト対象の StockService.java、src/test/java/demo にテストを置き、依存に junit-jupiter と mockito-junit-jupiter を宣言して mvn test で実行します。
package demo;
interface StockRepository { int count(String sku); }
interface Mailer { void send(String to, String subject); }
public class StockService {
private final StockRepository repo;
private final Mailer mailer;
public StockService(StockRepository repo, Mailer mailer) { this.repo = repo; this.mailer = mailer; }
public void notifyIfEmpty(String sku) {
if (repo.count(sku) == 0) {
mailer.send("[email protected]", sku + " is out of stock");
}
}
}
テストは次の StockServiceTest.java です。検証時はJDK 25・JUnit 6.1.3・Mockito 5.24.0(mockito-junit-jupiter)を使い、POMで maven.compiler.release を25、maven-surefire-plugin を3.6.0に指定して2件とも通りました(JUnit 6はSurefire 3.0.0以上が必要です)。
package demo;
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class StockServiceTest {
@Mock StockRepository repo; // スタブとして使う
@Mock Mailer mailer; // モックとして使う
@Test
void sendsOnceWhenEmpty() {
when(repo.count("SKU-1")).thenReturn(0);
new StockService(repo, mailer).notifyIfEmpty("SKU-1");
verify(mailer, times(1)).send("[email protected]", "SKU-1 is out of stock");
verifyNoMoreInteractions(mailer);
}
@Test
void doesNotSendWhenInStock() {
when(repo.count("SKU-1")).thenReturn(3);
new StockService(repo, mailer).notifyIfEmpty("SKU-1");
verifyNoInteractions(mailer);
}
}
MockitoExtension は厳格スタブ(STRICT_STUBS)で動くため、テスト内で使われないスタブを書くと UnnecessaryStubbingException で失敗します。上のテストに別のSKU向けの when(repo.count("SKU-2")).thenReturn(5) を足すと「Unnecessary stubbings detected.」で落ちることを確認しました。不要なスタブを溜め込まない仕組みとして、外さずに使うほうが安全です。
もう1つ、JDK 21以降ではMockitoが自分をエージェントとして読み込む方式に警告が出ます。上の構成でも、何も設定しないとテストは通るものの「Mockito is currently self-attaching to enable the inline-mock-maker. This will no longer work in future releases of the JDK.」と表示されました。Mockitoのjavadoc(0.3節)が示すとおり、Mavenでは次のようにPOMへ追記して -javaagent を明示すると警告は消えます(該当部分の抜粋で、properties と build/plugins にそれぞれ足します)。@{argLine} はプロパティが存在しないと文字列のままJVMに渡って起動に失敗する、とjavadocが注意しているので、空の argLine プロパティも一緒に定義します。
<properties>
<argLine></argLine>
</properties>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution><goals><goal>properties</goal></goals></execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>@{argLine} -javaagent:${org.mockito:mockito-core:jar}</argLine>
</configuration>
</plugin>
static メソッドやコンストラクタのモック化で、旧来のPowerMockitoからMockito本体の機能へ移る判断はPowerMockitoとは?静的・privateメソッドのMock化とMockitoネイティブへの移行判断で扱っています。
PHPUnit 13:createStubとcreateMockの使い分けと期待値なしモックの通知
PHPUnitはスタブとモックを作る関数そのものが分かれています。PHPUnit 13.4のドキュメントは、モックを「呼ばれ方の期待を追加で設定できるテストスタブ」と説明しています。プロジェクト直下で composer require --dev phpunit/phpunit:^13 を実行し、以下を tests/NotifyTest.php として保存して、vendor/bin/phpunit tests/NotifyTest.php を実行するとPHP 8.5.8・PHPUnit 13.4.1で通ります。
<?php
use PHPUnit\Framework\TestCase;
interface StockRepository { public function count(string $sku): int; }
interface Mailer { public function send(string $to, string $subject): void; }
function notifyIfEmpty(StockRepository $repo, Mailer $mailer, string $sku): void
{
if ($repo->count($sku) === 0) {
$mailer->send('[email protected]', $sku . ' is out of stock');
}
}
final class NotifyTest extends TestCase
{
public function testSendsOnceWhenEmpty(): void
{
$repo = $this->createStub(StockRepository::class);
$repo->method('count')->willReturn(0);
$mailer = $this->createMock(Mailer::class);
$mailer->expects($this->once())
->method('send')
->with('[email protected]', 'SKU-1 is out of stock');
notifyIfEmpty($repo, $mailer, 'SKU-1');
}
}
PHPUnit 12.5.0(2025年12月5日)からは、createMock で作ったのに expects を1つも設定していないテストに通知が出ます。試しにリポジトリを createMock にして戻り値だけを設定すると、PHPUnit 13.4.1では「No expectations were configured for the mock object for StockRepository. Consider refactoring your test code to use a test stub instead.」と表示されました。戻り値しか使わないなら createStub に直す、というのがPHPUnit自身の指示です。
RSpec:allowはスタブ、expectはモック、have_receivedはスパイ
RSpecはスタブとモックの違いが構文にそのまま出ます。allow(...).to receive は受け取ってよいメッセージを設定するスタブ、expect(...).to receive は受け取るべきメッセージを先に宣言し、満たされなければexampleの終了時に失敗させるモックです。gem install rspec で導入し(rspec コマンドにPATHが通っている前提)、以下を notify_spec.rb として保存して、rspec notify_spec.rb を実行するとRuby 4.0.6・rspec-core 3.13.6で2件とも通ります。
class Mailer
def deliver(to, subject); end
end
def notify_if_empty(repo, mailer, sku)
mailer.deliver("[email protected]", "#{sku} is out of stock") if repo.count(sku).zero?
end
RSpec.describe "notify_if_empty" do
it "allowはスタブ、expectはモック" do
repo = double("repo")
allow(repo).to receive(:count).and_return(0)
mailer = instance_double(Mailer)
expect(mailer).to receive(:deliver).with("[email protected]", "SKU-1 is out of stock").once
notify_if_empty(repo, mailer, "SKU-1")
end
it "have_receivedは実行後に検証するスパイ" do
mailer = instance_spy(Mailer)
notify_if_empty(double("repo", count: 0), mailer, "SKU-1")
expect(mailer).to have_received(:deliver).with("[email protected]", "SKU-1 is out of stock").once
end
end
モックが本物とずれる問題と、検証付きダブルでの防ぎ方
モックは本物のクラスと無関係に作れるため、本物のメソッドの引数が変わってもモックを使ったテストは緑のまま通ります。単体テストは全部通るのに、本番では引数不足で落ちる、という壊れ方です。
Pythonで、本物の send に引数 body が増えた状況を再現しました。素の MagicMock は古い呼び方を受け入れ、create_autospec で本物の定義から作ったモックだけが失敗します。
from unittest.mock import MagicMock, create_autospec
class Mailer:
def send(self, to, subject, body): # 本物は引数が3つに増えた
pass
loose = MagicMock()
loose.send("[email protected]", "SKU-1 is out of stock")
print("MagicMock: 通過")
strict = create_autospec(Mailer, instance=True)
try:
strict.send("[email protected]", "SKU-1 is out of stock")
except TypeError as e:
print("create_autospec:", e)
実行結果は「MagicMock: 通過」と「create_autospec: missing a required argument: ‘body’」でした。同じ仕組みは他の言語にもあります。
- RSpec:
instance_double(Mailer)は本物に無いメソッドのスタブを「the Mailer class does not implement the instance method」で拒否し、引数の数が違えば「Wrong number of arguments. Expected 3, got 2.」で失敗します。doubleではこの検査は働きません。 - Jest:
jest.fn()は引数の数を一切見ません。本物のオブジェクトにjest.spyOnを使えば存在しないメソッドは「Property `deliver` does not exist in the provided object」で止まりますが、引数の数は検査しません(3引数のメソッドを2引数で呼んでも通ることを確認しました)。また、既定では本物の実装も実行されます。 - Mockito:インターフェースやクラスから作るため、メソッド名や引数の食い違いはコンパイルの時点でエラーになります。
- PHPUnit:生成されるダブルが本物と同じシグネチャを持つので、引数が足りなければ実行時に
ArgumentCountError(Too few arguments)で止まります。
Python・Ruby・JavaScriptで、本物の定義と無関係なテストダブルを動的に作る場合は、Pythonでは create_autospec、RSpecでは instance_double でモックを本物の定義から作るのを既定にすると、メソッドの存在と引数の整合まで検査できます。JavaScriptは jest.spyOn で存在だけは確かめられ、引数まで守るにはTypeScriptの型検査を併用します。それでも外部APIの仕様変更は防げないので、HTTPの境界では契約テストやAPIモックの更新運用が別に必要になります。この分担はAPIモッキングとは?モックサーバーの方式選定と契約ドリフト対策を実装目線で解説で扱っています。
モックを使う場面・使わない場面と、過剰モックの是正
モックが有効な場面と、実物・フェイクで代替すべき場面
PHPUnit 13.4のドキュメントのBest Practicesは「Mock service boundaries (repositories, external services), not domain objects or value objects.」と、リポジトリや外部サービスといった境界をモックし、ドメインオブジェクトや値オブジェクトはモックしない方針を示しています。この記事ではさらに、モックで検証するのは依存の呼ばれ方そのものが仕様になっている場面に絞ります。外部への通知、決済APIの呼び出し、イベントの発行がその例です。逆に、自分で書いた値オブジェクトや副作用の無い計算はモックせず、本物のまま呼びます。保存した値を後で読み出すような、読み書きを伴う利用側のロジックは、インメモリのフェイクを候補にします。ただし、SQLやトランザクションなど本物のDBとの整合はフェイクでは確かめられないので、実DBを使う結合テストに残します。迷ったら、副作用の無い計算や値オブジェクトは本物を使い、呼ばれ方が仕様である依存はモック、入力を固定する依存はスタブ、読み書きの状態を再現する依存はフェイクを選んでください。
過剰モック(モック地獄)の兆候と是正手順
あらゆる依存をモック化すると、テストは実装の手順(どのメソッドをどの順で呼ぶか)に密着し、振る舞いが同じリファクタリングでも失敗するようになります。兆候は具体的に見分けられます。
- 1つのテストのモック設定が、検証したい処理より長い
- 副作用の無いメソッドの呼び出し回数まで
verifyしている - Mockitoで
lenient()を多用して厳格スタブを黙らせている、PHPUnitで期待値なしモックの通知が大量に出ている - 実装を整理しただけのコミットで、大量のテストが赤くなる
是正は、モックを実物とフェイクへ降ろす方向で進めます。副作用の無い依存はモックを外して本物を呼び、DB系はフェイクに置き換え、verify は仕様として必要な呼び出しだけに絞り、実装内部の手順は検証しません。verifyNoMoreInteractions のような「ほかは何も呼ばれていない」検証は、検証済みの呼び出し以外が無いこと自体を仕様として保証したい場合に使います。同じ引数での二重送信を防ぎたいだけなら、times(1) で回数を検証すれば足ります。モックを減らしたあとにテストが本当に不具合を検出できるかは、ミューテーションテストとは?ミュータント生成とスコアの読み方を実装者向けに解説の手法で測れます。
テストピラミッドの中でのモックの位置と、E2Eとの分担
モックは単体テストを速く安定させるための道具で、テスト全体の配分と切り離せません。依存をモックやスタブで切り離せるから、単体テストを土台として厚く積めます。配分の考え方はテストピラミッドとは?単体・結合・E2Eの配分と実装者向けテスト戦略設計で詳しく書いています。
ただし、モックに置き換えた依存とのつなぎ目は、その単体テストでは検証できません。本物同士の接続やデータの受け渡しは結合テストで、主要な利用導線はE2Eで確かめます。前の章で見た引数のずれも、こうした実物同士のテストで初めて表に出ます。E2Eの範囲の絞り方はE2E(エンドツーエンド)テストのベストプラクティス|結合テストとの違いとツール選定で扱っています。チームでは、呼び出しを検証する境界と実物を使う範囲を共有し、実装内部の呼び出し順まで固定するテストが増えていないかをコードレビューで確認します。既存システムの改修と並行してテスト基盤や方針を整える余力が無い場合は、Webシステムの保守運用・内製化支援で設計から定着まで伴走できます。
モックとスタブについてよくある質問
テストでモックとは何ですか?
依存オブジェクトの代わりに置き、その依存が期待どおりに呼ばれたか(回数・引数)を検証する代用品です。メール送信などの副作用を実際には起こさず、送信処理が期待した回数・引数で呼び出されたことを確かめるために使います。
単体テストにおけるスタブとは何ですか?
テスト対象が呼び出す依存の代わりに、設定した値を返す代用品です。DBや外部APIの応答を固定し、テスト対象の戻り値や状態だけを確かめます。結合テストでは、呼ぶ側の代用品であるドライバと対で使われます。
スタブのデメリットは何ですか?
スタブが返す値は書き手が決めたものなので、本物の依存の応答が変わってもテストは気づきません。本物の定義と無関係に作ったスタブでは、引数の数が変わってもテストが通ることがあります。Pythonの検証付きダブルは呼び出しのシグネチャを検査しますが、型注釈に従った引数の型までは検査しないため、別途型チェッカーが必要です。Pythonの create_autospec やRSpecの instance_double で本物の定義から作るか、結合テストで本物とのつなぎ目を確かめる必要があります。
RSpecのスタブとモックの違いは何ですか?
allow(obj).to receive(:m) がスタブで、呼ばれなくても失敗しません。expect(obj).to receive(:m) がモックで、example終了までに呼ばれなければ失敗します。実行後に確かめたいときは、instance_spy などで作ったダブルに対して expect(obj).to have_received(:m) を使うスパイの書き方を選びます。
モックは使わないほうがよいのですか?
使わないのではなく、通知・登録・決済のように呼ばれ方が仕様である依存に絞って使います。純粋な計算や値オブジェクトは本物のまま呼ぶほうが、リファクタリングで壊れにくいテストになります。