ポンコツエンジニアのごじゃっぺ開発日記。

いろいろポンコツだけど、気にするな。エンジニアの日々の開発などの記録を残していきます。 自動で収入を得られるサービスやシステムを作ることが目標!!

【Mockery】テストでstaticメソッドをモックするやり方【PHP】

PHPテストコードを書いていて、モックしたい場合にMockeryはよく使われるツールかなと思います。PHPで有名なフレームワークのLaravelでも使われてますね。

github.com

今回はそのMockeryでstaticメソッドをモックするときにちょっと躓いたので、記事にメモしておきます。

環境

言語やツールのバージョンは以下の通りです。だいたい現時点の最新バージョンなはずです。

ツール名等 バージョン等
PHP PHP 8.1.3 (cli)
PHPUnit 9.5.20
Mockery 1.5.0

staticメソッドをモックする

では、早速モックしていきたいと思います。

ドキュメントを確認する

Mockeryのドキュメントを確認すると、こう書いてあります。

docs.mockery.io

Static methods are not called on real objects, so normal mock objects can’t mock them. Mockery supports class aliased mocks, mocks representing a class name which would normally be loaded (via autoloading or a require statement) in the system under test. These aliases block that loading (unless via a require statement - so please use autoloading!) and allow Mockery to intercept static method calls and add expectations for them.

英語なのでDeepLを使って日本語に翻訳してみます。

静的メソッドは実際のオブジェクトからは呼び出されないので、 通常のモックオブジェクトではモックできません。Mockeryはクラスエイリアスモックをサポートしており、通常テスト対象のシステムで(オートローディングやrequire文によって)ロードされるであろうクラス名を表すモックです。このエイリアスは、(require文がない限り)ロードをブロックし、 Mockeryが静的メソッドの呼び出しを傍受し、それに対する期待値を追加することを可能にします。

静的オブジェクトなので、普段使っているモックオブジェクトでモックできないよ。クラスエイリアスってのをサポートしているからそれを使うと実現できるよ。

みたいなことでしょうか。

その下に、

See the Aliasing section for more information on creating aliased mocks, for the purpose of mocking public static methods.

と書いているので、Aliasingのページを見てみたいと思います。

docs.mockery.io

Prefixing the valid name of a class (which is NOT currently loaded) with “alias:” will generate an “alias mock”. Alias mocks create a class alias with the given classname to stdClass and are generally used to enable the mocking of public static methods. Expectations set on the new mock object which refer to static methods will be used by all static calls to this class.

どうやらクラス名の前にalias:と付けるとエイリアスモックが生成されて、静的メソッドをモックできるようになるよってことらしいです。

<?php

$mock = \Mockery::mock('alias:MyClass');

↑このような感じでかけるらしいです。

実際に書いてみる

今回はSymfony\Component\Yaml\YamlparseFile()というメソッドをモックしたかったので、それを例に書いていきます。

symfony.com

<?php

\Mockery::mock('alias:' . Yaml::class)->shouldReceive('parseFile')
    ->with('任意のファイルパス')
    ->andReturn([パースされた内容]),

このようにすることで、処理内でparseFile()を呼んでいるところが想定通りにモックして返してくれるようになりました。

「Mockery\Exception\RuntimeException: Could not load mock Symfony\Component\Yaml\Yaml, class already exists」エラー

単体テストは上記の対応で問題ありませんでした。しかし、全体テストを実行したら以下のようなエラーが発生しました。

Mockery\Exception\RuntimeException: Could not load mock Symfony\Component\Yaml\Yaml, class already exists

参考サイト:MockeryでoverloadをつかってテストしたらCould not load mockとか言われる件 - Qiita

テストに@preserveGlobalState disabledのアノテーションを付ければ解決とのことです。

phpunit.readthedocs.io

<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;

final class MyTest extends TestCase
{
    /**
     * @runInSeparateProcess
     * @preserveGlobalState disabled
     */
    public function testInSeparateProcess(): void
    {
        // ...
    }
}

上記はphpunitのドキュメントのソースなのですが、このように@runInSeparateProcess@preserveGlobalState disabledを付ければ良いとのことです。

お問い合わせプライバシーポリシー制作物