📝

PHPUnit × Mockery の overload を使うとテストが不安定になる問題と対処法

に公開

PHPUnit と Mockery を使ってユニットテストを書いている中で、
Mockery の overload: を使用したテストが 実行順序によって落ちたり通ったりする という問題に遭遇。

ローカルでは通るのに CI で落ちる ―
理由はシンプルですが、知っておかないとハマるポイントだったのでまとめておきます。


結論

  • Mockery の overload:クラス定義そのものをモックに置き換える
  • そのため 同じプロセスで複数回実行されるとエラーになる
  • PHPUnit の実行順やプロセス分割の仕方によって成功・失敗が変わる
  • ローカルと CI で挙動が変わる原因にもなる
  • 基本的に overload は使わない方がよい。DI で差し替え可能な設計にした方が安定する

1. Mockery の overload とは?

通常のモック

$mock = Mockery::mock(SomeClass::class);
// 新しいモックオブジェクトを作るだけ

overload モック(危険枠)

$mock = Mockery::mock('overload:' . SomeClass::class);
// クラス定義そのものを書き換える

overload: は “差し替え” ではなく “上書き” を行う。

そのため正しく動けば強力だが、テスト環境とは非常に相性が悪い。


2. なぜ実行順序で落ちるのか

overload: の動作は次のとおり。

1. テストAで SomeClass がロードされる
2. Mockery がクラス定義そのものをモックに置き換える
3. 同じプロセスでテストBが続けて実行される
4. 再び overload を実行
5. 「クラスはすでに存在します」 → class already exists エラー

つまり、「同一プロセス内で2回以上 overload が走るとアウト」


3. 実際に発生するパターン

成功パターン

TestA(overload なし)
TestB(overload あり)
--- プロセスが切り替わる ---
TestC(overload あり)
→ 問題なし

失敗パターン

TestA(overload あり)
TestB(overload あり)
→ 同じプロセスで連続実行 → エラー

4. なぜローカルは通るのに CI では落ちるのか?

これは、CI がテストを分割したり、並列実行したり、
内部で複数のプロセスを扱ったりするため 実行順が不安定になる のが原因。

たとえば:

  • ローカル
    → テスト実行順がほぼ固定で、overload が連続しないため問題が出にくい

  • CI
    → テストを複数のグループに分けて実行する
    → グループの順序・プロセスの切り替わり方が変わる
    → たまたま overload テストが同じプロセスに固まる
    → 落ちる

“ローカル OK なのに CI だけ落ちる”
という典型的な不安定テストの原因になる。


5. 解決策

1. overload を使わない(最良)

DI(依存注入)を使ってテスト可能な設計にする。

class SomeService
{
    public function __construct(
        private SomeClass $client
    ) {}
}

テスト側は通常のモックを渡せばよい。


2. テストを別プロセスで実行する(応急処置)

/**
 * @runInSeparateProcess
 * @preserveGlobalState disabled
 */

3. テストクラス全体を別プロセスにする

/**
 * @runTestsInSeparateProcesses
 */
class SomeServiceTest extends TestCase

ただし、これらはあくまで「モックを安全に使うための対症療法」。


6. まとめ

  • Mockery の overload: はクラス定義を破壊するのでテスト環境と相性が悪い
  • PHPUnit の実行順やプロセス構成が変わると、成功・失敗が変わってしまう
  • ローカルと CI の挙動差を生む最大の原因になる
  • 基本的には使わず、DI で差し替える設計にするのがベスト

7. 個人的な学び

  • テストは「プロセスに依存した瞬間に不安定化する」
  • “クラス定義を書き換えるモック” は長期的に必ずトラブルになる
  • シンプルな DI 設計が一番テストしやすく、環境差異も出ない
  • ローカル成功=安全 ではない。CI で崩れる余地を常に考える

Discussion