composition される final クラスを mock してテストを書きたい
お久しぶりです。ぐーどらです。
ここ数日個人的な困りごととして、掲題の悩みを抱えていました。
composition される final クラスを mock してテストしたいけど難しい。
— ぐーどら (@CIOguldra) 2023年2月23日
- テストのために final を外す ← 論外
- Interfaceを切る ← 手段と目的の逆転を感じる
だれか設計教えて
テストするために Interface 切り、コンストラクタの引数を Interface にすることも考えました。 しかし今回はテスト対象のクラスがコンポジションするべきはmockするクラスであることを明示したい状況であったため、Interfaceを切る方針も却下しました。 結果的にTwitterで声をかけていただいた、局所的なrequireでクラスをオーバーライドすることでテストを通すことにしたので覚書にしようと思います。
同じ名前空間・同じクラス名になる差し替えクラスを、局所的な require もしくは composer.json の files で読み込むぐらいしか思いつかないですね。前者であればプロセス分離されたテストにすると良さそう
— 名前変え放題制度 (@mpyw) February 23, 2023
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.php
を tests/Overrides/Compositional.php.stub
に変更してみました。
こうすることでPhpStormでの警告も消え、無事にテストもmock出来るようになりました。
@mpywさんありがとうございました! どうやったらこんな解決方法思いつくんや…
他にこんな方法知ってるぜって方いらっしゃいましたらぜひ教えて下さい。