APIをモック化したい
Laravelでテストコードを書く時に、APIをPHPUnitのMockeryを使用してモック化したい時がありましたので備忘録として残しておきたいと思います!
既存のコードで、API部分は一切テストが書かれていない・・・。
というのは多々あることかと思いますが、それだと稼働中のサービスで改修やリファクタリングをするのがかなり厳しく、非常に困りますよね!
またFat ControllerやFat Model状態で、メソッド内でnewしまくっているなど、めちゃくちゃ依存性が高いものは、テストがほとんど書けないですし、一部改修するだけでも非常に苦労が多くなります。
クラスの役割を明確にして、個々はできるだけ疎結合な状態になるように書けると、テストもガッツリ書けるし、いいですね!
ということでMockeryでモック化する方法をメモとして残しておきたいと思います!
設計面での前提事項
今回掲載しているテストコードの設計面で前提となる部分を掲載しておきます!
まずDAOやAPIへアクセスする部分は、Repository層で隠蔽しています。
また各クラスに依存するのではなくて、必ずinterfaceへ依存するように書きます!
Laravelのサービスコンテナーで、インターフェイスと実装の結合をすることで、テストを書く際はこれを簡単にMockに置き換えられます!
Repository層
例えば、RepositoryのfetchCustomersを呼んだら、customersが取得できるとします。
Repository層で隠蔽することで、customersをデータベースからとってきているのか、APIから取得してきているのかは意識しなくて良いような感じになります。
仮に今までcustomersをデータベースから取得してきていたものを、APIから取得するように変わっても、各サービス層をRepositoryのInterfaceへ依存させておけば、簡単に切り替えられます。
以下は実装イメージになります。
SomethingRepositoryInterface.php
<?php
interface SomethingRepositoryInterface
{
public function fetchSomething(): SomethingObjects;
}
SomethingRepository.php
<?php
class SomethingRepository implements SomethingRepositoryInterface
{
public function __construct(SomethingDao $somethingDao)
{
$this->somethingDao = $somethingDao;
}
public function fetchSomething(): SomethingObjects
{
return new SomethingObjects(
...$this
->somethingDao
->select()
->map(function ($obj) {
return $this->toSomethingEntity($obj);
})
);
}
}
サービスコンテナー
Laravelのサービスコンテナーで、インターフェイスと実装の結合をすることで、テストを書く際は、サービスコンテナを使用して簡単にMockに置き換えられます!
RepositoryServiceProvider.php
<?php
class RepositoryServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(SomethingRepositoryInterface::class, SomethingRepository::class);
}
public function boot()
{
}
}
サービスコンテナーの詳細は以下の公式サイトに詳しく掲載されています。
Mockeryを使用したテスト
ここでは、課金のキャンセル処理のテストを書く設定にしたいと思います。
課金のキャンセルは、DB内の課金情報の変更(or 削除など)と決済サービスAPIなどのキャンセル処理が必要だとします。
この決済サービスAPIのキャンセル処理があるために、外部サービスに依存してしまいテストが書けません。
そのためこの部分をモック化したいと思います。
RepositoryとServiceProviderの実装イメージ
以下が課金のキャンセル処理のRepositoryとServiceProviderの実装イメージになります。
SomethingPaymentApiRepositoryInterface.php
<?php
interface SomethingPaymentApiRepositoryInterface
{
public function cancel(): something;
}
SomethingPaymentApiRepository.php
<?php
class SomethingPaymentApiRepository implements SomethingPaymentApiRepositoryInterface
{
public function cancel(): something
{
// APIのキャンセル処理
}
}
RepositoryServiceProvider.php
<?php
class RepositoryServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(SomethingPaymentApiRepositoryInterface::class, SomethingPaymentApiRepository::class);
}
public function boot()
{
}
}
モック化する方法
SomethingPaymentApiRepositoryをモックで置き換えたいと思います!
RepositoryServiceProviderで、インターフェイスと実装の結合をしていましたが、実装の部分をモックに置き換えれば完了です!
$this->app->bindで、SomethingPaymentApiRepositoryInterfaceの実装をモックで置き換えています!
他にも$this->partialMockなど色々モック化の方法があるので、詳細は公式サイトを見てください!
サンプルコード
こちらが課金のキャンセル処理のテストコードになります!
APIのキャンセル処理をモック化することで、APIのキャンセル処理に依存することなく、課金キャンセル処理がちゃんと動作するかテストすることができますね!
<?php
use Mockery;
class CancelPaymentTest extends TestCase
{
public function testSuccessCancelPayment()
{
$mock = Mockery::mock(SomethingPaymentApiRepository::class)
->shouldReceive('cancel')
->andReturn([
'somethingReturnKey1' => 'somethingReturnVal1',
'somethingReturnKey2' => 'somethingReturnVal2',
])
->getMock();
$this->app->bind(SomethingPaymentApiRepositoryInterface::class, function () use ($mock) {
return $mock;
});
$response = $this->post(route('api.payment.cancel'), [
'somethingParamKey' => 'somethingParamVal'
]);
// DBのcancel処理結果などをassertする。
$this->assertSomething();
}
}
※$this->app->bindではなくて、$this->instanceの方がいいかもなので今度試してみたら更新しておこうと思います。
参考サイト
以下は参考サイトになります。
まとめ
今回は、LaravelでMockeryを使用して、APIをモック化してテストコードを書く方法を書きました!
テストを書くとなると設計もある程度考えて、テストしやすいコードを書く必要も出てくると思うので、僕自身も頑張っていきたいと思います!
実際のところ最近はLaravelをほとんど触っていないため、記事におかしなところがあればご指摘ください!
コメント