公開メソッドの中でprivateメソッドを呼び、その中で外部へ通知している。通知の失敗をテストしたくて「Mockito private モック」と検索した。
この場合、置き換えたいのはprivateという実装上の区切りではなく、外部通信です。この記事では、通知サービスを例に、privateの名前に依存せず、通信失敗時の動きを確認する形へ変えます。
新しく書くテストでは、まず通信を差し替えられる依存にする。
純粋な整形処理は公開メソッドの結果から確認し、成功・失敗を作りたい外部依存を置き換えます。既存のPowerMockテストを保守する場合は、別の作業として実行環境を固定します。
Mockitoでprivateメソッドを直接モックする機能はない
Mockito公式FAQは、privateメソッドのモックを提供しないと説明しています。staticやfinalを扱えるかという話とは別です。
「モックしたいからpublicへ変える」のも、最初の選択にはしません。呼び出し元に公開する必要がない内部処理を、テストの都合だけでAPIに増やすからです。
先に、テストの目的を「通知に失敗したら、呼び出し元へ失敗を伝える」のように言い換えます。その結果を作るには、何を置き換えればよいかを考えます。
通知送信だけを受け取る形にする
次はJava標準APIだけで動く説明用のコードです。通知本文の整形はprivateのまま、外部送信だけを依存として受け取ります。
interface Sender {
void send(String text);
}
class NotificationService {
private final Sender sender;
NotificationService(Sender sender) {
this.sender = sender;
}
public String notify(String subject) {
if (subject == null || subject.isBlank()) {
throw new IllegalArgumentException("件名が必要です");
}
sender.send(format(subject));
return "送信済み";
}
private String format(String subject) {
return "通知: " + subject;
}
}この例の契約は、空の件名を拒否し、送信が成功したときだけ「送信済み」を返すことです。送信先の実行時例外は呼び出し元へ伝播します。リトライや送信履歴の保存までは扱いません。
成功と失敗を、通信せずに作れる
成功時は、送られた文字列を記録するSenderを渡します。
var sent = new java.util.ArrayList<String>();
var service = new NotificationService(sent::add);
String result = service.notify("処理完了");
// resultが「送信済み」、sentが「通知: 処理完了」の1件か確認する失敗時は、例外を投げるSenderを渡します。
var failure = new IllegalStateException("送信先で失敗");
var service = new NotificationService(text -> { throw failure; });
// service.notify("処理完了")がfailureを呼び出し元へ伝えるか確認するprivateメソッドの名前は、テストから指定していません。内部の整形方法を変えても、公開された結果と送信内容が同じなら、その契約を引き続き確認できます。
この小さな例はJava 17.0.8で、成功時の結果と送信文、空白の拒否と未送信、送信失敗の伝播を確認しています。Mockito・PowerMockを動かした検証ではありません。
privateを全部別クラスにする必要はない
分ける基準はアクセス修飾子ではなく、依存の性質です。
| 処理 | 今回の推奨 |
|---|---|
| 渡された文字列を整形する | 公開メソッドの結果や送信内容から確認する |
| 通信、DB、ファイルなどの外部依存 | 必要な成功・失敗を作れる境界として分ける |
| 長大な業務判断がprivateに詰まっている | 独立した責務として説明できるか見直す |
一行の整形のためにクラスを増やすと、読む場所だけ増えることもあります。一方、外部通信まで同じクラスへ閉じ込めているなら、テスト以外にも変更理由を分ける価値があります。
既存のPowerMockテストは、環境ごと扱う
すでにPowerMockでprivateを指定しているテストの保守なら、いきなり全廃はしません。まずCIで動いているJDK、JUnit、Mockito、PowerMockの版と実行コマンドを記録し、対象テストを再現します。
PowerMock公式Wikiには、Runnerや@PrepareForTestなどの準備、互換性に関する注意があります。数行のPowerMockito呼び出しだけを抜き出して、現在の環境へ追加する手順にはしないでください。
今回、PowerMockの組み合わせの再検証はしていません。新規の推奨手順として未検証の依存セットを載せるのではなく、上の通信境界を使う方法を勧めます。変更承認の範囲が狭い保守では、既存テストを維持し、今回触る境界から少しずつ移す判断もできます。
モックの操作より、何を保証するかを学ぶ一冊
『単体テストの考え方/使い方』は、テストを実装の細部へ結びつけすぎず、何を保証するか考えたい人向けです。C#の例を使う本なので、JUnitの書き方を覚える目的とは分けて選びます。出版社の紹介
※Amazonのリンクにはアフィリエイトを含みます。
いまprivateをモックしたくなった処理を、「どんな条件で、外へ何が起きればよいか」に言い換えてみてください。置き換えるべき相手は、privateメソッドではなく、その奥の依存かもしれません。
