酒と開発の日々

飲酒大好き駆け出しエンジニアのブログ

composition される final クラスを mock してテストを書きたい

お久しぶりです。ぐーどらです。

ここ数日個人的な困りごととして、掲題の悩みを抱えていました。

テストするために Interface 切り、コンストラクタの引数を Interface にすることも考えました。 しかし今回はテスト対象のクラスがコンポジションするべきはmockするクラスであることを明示したい状況であったため、Interfaceを切る方針も却下しました。 結果的にTwitterで声をかけていただいた、局所的なrequireでクラスをオーバーライドすることでテストを通すことにしたので覚書にしようと思います。

Compositional.php
final class Compositional
{
  // doSomething
}

TestTargetCalss.php
final class TestTarget
{
  public function construct(private readonly Compositional $conpositional) {
  }
}

CompositionalClassも別の箇所から呼び出されるようなパッケージだし、final取るのも、TestTargetの引数をInterfaceにするのも違うよなあ…と苦慮していたのですが、同じ名前空間・同じクラス名でオーバーライドするというのは目からウロコでした。 早速やってみることに。

$ tree
.
├── Packages
│   └── SomePackage
│       ├── Compositional.php
│       └── TestTarget.php
└── tests
    ├── Overrides
    │   └── Compositional.php
    └── Packages
        └── TestTargetTest.php


composer.json
    "autoload-dev": {
        "psr-4": {
            "Tests\\": "tests/"
        },

TestTargetTest.php
require (__DIR__ . '/../Overrides/Compositional.php')

public function test_finalClassMockTest()

このようなディレクトリ構成とautoloaderの外にstubファイルとして配置することで、本番環境でも動作しないことを担保できるようにしました。

しかし一つだけ問題が… 定義する名前空間が同一であるため、PhpStormで警告が出てきました。 ぐぬぬ。許せぬ。

ここで妙案が。拡張子変えてしまえば良いのでは?と思いつき、tests/Overrides/Compositional.phptests/Overrides/Compositional.php.stub に変更してみました。 こうすることでPhpStormでの警告も消え、無事にテストもmock出来るようになりました。

@mpywさんありがとうございました! どうやったらこんな解決方法思いつくんや…

他にこんな方法知ってるぜって方いらっしゃいましたらぜひ教えて下さい。