Yii2でPHP Unitを書くのにAspectMockを導入してハマったところメモ

# はじめに

とても便利なモッキングフレームワークであるAspectMockの導入で地味にハマったので記録メモになります。
正直なところ、環境によるものが多いので他であまり参考にならないかなと思いつつ、今後導入することがある時や、導入に困っている方のヒントになれば、と思いメモ書き程度に書いていきます。

いろいろ試しすぎていて何を試したか全てを覚えているわけではないので、なぐり書きっぽい感じになってしまっているのはご容赦ください :pray: 

# そもそも、AspectMockとは

利用している人も多いと思いますが、AspectMockは非常にパワフルなモッキングフレームワークです。
以前の現場で使っていた時も、なんでもできるなという印象があったので、現在の現場でも使うことになった次第です。
https://github.com/Codeception/AspectMock


### 参考記事

- [普通じゃないモッキングフレームワークAspectMockがパワフル過ぎる](http://blog.a-way-out.net/blog/2014/12/10/aspect-mock/)
- [AspectMock 最強のモッキングフレームワーク](https://www.slideshare.net/kenjis/20141116-aspect-mock)

# AspectMockの導入

AspectMockを使うために、composerを使ってインストールします。

```
$ composer require --dev codeception/aspect-mock
```

# ハマっていたところ

今回は、commonディレクトリにあるServiceクラスのテストを書きたく、呼び出しているModel側をモックにしてDBからデータを取らないようにして、色々なパターンのテストを書こうと思っていました。

ですが、AspectMockでテストダブルを書いているのに、指定したモック用のキャッシュディレクトリにファイルが作成されず、モック化しているModelがDBからデータを引いてきているようで、モックの作成がうまく行っていないようでした。

## 環境

- PHP v7.2.22
- Yii2 Framework v2.0.22
- codeception v3.0.2
- PHP Unit v8.2.4
- codeception/aspect-mock v3.1.0

```txt:ディレクトリ構成（ざっくり。結構割愛）
app
|-- backend
|   `-- tests
|-- common
|   |-- codeception.yml
|   |-- models
|   |-- services
|   `-- tests
|       |-- _bootstrap.php
|       |-- unit
|       |   |-- models
|       |   `-- services
|       `-- unit.suite.yml
`-- frontend
    `-- tests
```

### 1. bootstrap

[公式（codeception/aspect-mock）にあるbootstrapの設定方法](https://github.com/Codeception/AspectMock#configuration) や、その他色々な記事にあるようなbootstrapの書き方をしたのですが、どうも現環境ではうまく動かない様子。

includePathsに指定しているのに、テストダブルで指定しているクラスが中々モックが作成されず、$kernelのインスタンスに以下のように無理やり読み込ませてみましたが、これだとcommon配下のファイルが全てcacheDirで指定したキャッシュディレクトリに全コピーされるような挙動をされてしまい、あまりイケていませんでした。

```php
$kernel->loadPhpFiles(__DIR__ . '/../../common');
```
そのため、 上記のようなloadPhpFilesやloadFileを使わない方針で、他の解決策を模索し始めました。。

最終的には、bootstrapは公式で説明されているような以下のような形となりました（一部割愛）

```php:最終的な_bootstrap.php
<?php
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'test');
defined('YII_APP_BASE_PATH') or define('YII_APP_BASE_PATH', __DIR__ . '/../../');

require_once __DIR__ . '/../../vendor/autoload.php';
require_once __DIR__ . '/../../vendor/yiisoft/yii2/Yii.php';

$kernel = \AspectMock\Kernel::getInstance();
$kernel->init([
    'appDir' => YII_APP_BASE_PATH,
    'debug' => true,
    'includePaths' => [
        __DIR__ . '/../'
    ],
    'excludePaths' => [__DIR__ . '/../tests'],
    'cacheDir' => '/tmp/cache/AspectMock'
]);

// もしYii::setAlias等が必要であれば、必要な分だけこの_bootstrapに書く必要があります
```


### 2. php-parserのバージョンが古い

PHPアプリ側のメソッドで、nullableな指定をしているメソッドが、モックのキャッシュを作る時にパースエラーになってしまっている様子。
どうやら、 nikic/php-parserのバージョンが古いようなので、こいつをupdate。
（他のパッケージとの依存関係があったりするので、updateするのには要調査、、、調査してくれた同僚の @Yoika さんに感謝 :pray: ）

### 3. composer.jsonにautoloadの記載がない

composer.jsonに、autoloadを追記する必要があった。
各クラスに書いていたnamespaceはディレクトリの構成名通りなので、以下のように記載。

```json:composer.json
    "autoload": {
        "psr-4": {
            "common\\": "common",
            "frontend\\": "frontend",
            "backend\\": "backend"
        }
    }
```
上記を記載して、

```
$ composer dump-autoload
```
を実行。

このあとモックを使ったテストを書いているUnitTestを実行したら、無事cache配下に必要なファイルだけキャッシュファイルが作成され、モックから期待する結果が返ってきました :joy: 

# その他

一部割愛してしまいましたが、環境によるとは思いますが他にも細かいところとして、

-  `_bootstrap.php` に `require_once` で呼び出す必要のあるファイルがあった
-  AspectKernelで読み込むファイルで `Yii::getAlias()` を使用しているところがあり、 `Yii::setAlias()` を `_bootstrap.php` に書く必要があった


みたいなところがありました。

# おわりに

あまり記事を書かないので分かりにくかったり、読みにくい箇所が多くて申し訳ないですが、あくまでも記録用メモとして見ていただければ幸いです。

記事中には書きませんでしたが、現状のアプリでうまくいかない様子だったときに、別に新規ですぐに立ち上がるPHPのFrameworkを使ってみて、PHP Unit内でAspectMockがすぐに動くことが確認できたので、そこと`composer.json`の比較をしてみたり、`bootstrap.php`のの比較をしてみたりしました。
（今回は、興味本位で以下を参考にLaravelでWebアプリを立ち上げて比較・検証してみました）

### 参考記事
- [Laravelの開発環境をDockerを使って構築する](https://qiita.com/ucan-lab/items/17c806973e69792ada99)