🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
CakePHP

CakePHPは、PHPで書かれたWebアプリケーション開発用のフレームワークです。 Ruby on Railsの考え方を多く取り入れており、Railsの高速性とPHPの機動性を兼ね備えています。 MVCやORMなどを「規約優先の考え方」で利用するため、コードを書く手間を省くことができます。 外部のライブラリに依存しないので、単体での利用が可能です。

PHPUnit

PHPUnitは、PHP向けのユニット・テスト向けフレームワークで、手動では手間のかかるテスト作業を自動化し、繰り返し実行することが可能です。

API

APIはApplication Programming Interfaceの略です。APIはプログラムにリクエストされるサービスがどのように動作するかを、デベロッパーが定めたものです。

Q&A

解決済

1回答

3853閲覧

外部APIと連携しているコントローラーの単体テストで、例外発生ケースをテストする方法

退会済みユーザー

退会済みユーザー

総合スコア0

CakePHP

CakePHPは、PHPで書かれたWebアプリケーション開発用のフレームワークです。 Ruby on Railsの考え方を多く取り入れており、Railsの高速性とPHPの機動性を兼ね備えています。 MVCやORMなどを「規約優先の考え方」で利用するため、コードを書く手間を省くことができます。 外部のライブラリに依存しないので、単体での利用が可能です。

PHPUnit

PHPUnitは、PHP向けのユニット・テスト向けフレームワークで、手動では手間のかかるテスト作業を自動化し、繰り返し実行することが可能です。

API

APIはApplication Programming Interfaceの略です。APIはプログラムにリクエストされるサービスがどのように動作するかを、デベロッパーが定めたものです。

0グッド

0クリップ

投稿2019/11/29 02:33

前提・実現したいこと

外部APIへリクエストを投げた際、エラーになった場合の処理のテストを行いたい

発生している問題・エラーメッセージ

テスト用のコントローラーで不正な値を持たせた状態で、API側へ登録リクエストを送りエラーを発生させました
するとAPI側からエラーが返されテストが通りませんでした

Payjp\Error\InvalidRequest: No such token:

if(!$payjpCustomer->save()){ $result = 1; } $this->assertEquals(1, $result);

該当のソースコード(テスト対象のコントローラー)

try { $payjpCustomer = \Payjp\Customer::retrieve($payjpCustomerId); // 登録されているカード情報を削除する $cards = $payjpCustomer->cards->all()["data"]; foreach($cards as $card){ $card = $payjpCustomer->cards->retrieve($card["id"]); $card->delete(); } $payjpCustomerCardData = [ 'card' => $payjpToken ]; $payjpCustomerAddedCard = $payjpCustomer->cards->create($payjpCustomerCardData); $payjpCustomerAddedCardId = $payjpCustomerAddedCard['id']; // Set default card $payjpCustomer->default_card = $payjpCustomerAddedCardId; $payjpCustomer->save(); } catch (InvalidRequest $ex) { $this->log("error 01: " . $ex->getMessage(), "error"); $this->Flash->set("登録に失敗しました。もう一度、やり直してください。"); }

試したこと

テストコードを以下のようにも変更して見ました
当たり前ではあるのですが、これでもその前に書いたエラーがAPI側から返されます

$payjpCustomer->save(); $this->setExpectedException('Cake\Network\Exception\NotFoundException'); $this->post('/payments/pay/'. $Id);

大元のコントローラーではAPI側との通信であれ、その結果をDBに保存するタイミングであれ、何かしら問題が起きればtyr catchでエラーメッセージなどが表示されます

外部サービスと連携してそのような処理を行なっているコントローラーのテストコードを書こうとする場合、テストコードの中でもtyr catchを書いて挙動を見るものなのでしょうか?

もしくは、controllerに対して不正な値がリクエストされた場合に例外が発生する時はユニットテストで検証可能
外部APIなどに対してリクエストを投げてそれによって例外を取得し、振る舞いを変える場合はそのコントローラーで直接デバックしたり、フロント側から操作をすることによってテスト

このような使い分けがなされるのでしょうか?

補足情報(FW/ツールのバージョンなど)

cakephp 3.5
API payjp api

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

m.ts10806

2019/11/29 02:50

APIにはテスト用の情報をきちんと送れたのでしょうか。 決済サービスであれば必ずテスト用のパラメータが用意されていると思いますが
退会済みユーザー

退会済みユーザー

2019/11/29 03:21

無理やり不正な値を入れていたのですが、そういえばエラーが返るように設定されているものありますね・・・。 ご指摘いただいた通り、テスト用に用意されているものを使ってAPIからエラーを取得するように書き換えました あらかじめ用意されているエラーは返されたのですが、そのエラーメッセージがターミナルに表示されています 実際のコントローラーで同じことが起きた場合、登録し直すようにというメッセージが表示されるとともに今までの処理が取り消されるようになるのですが、そうなるか否かをこのテストで確認する方法はありますでしょうか?
m.ts10806

2019/11/29 03:29

PHPUnitの範囲ですよね。あくまで単体の機能テストなので、それをどこまで持たせるかというテスト設計次第とは思います
guest

回答1

0

ベストアンサー

前提として、外部APIとの接続を行う処理はコントローラーから直接呼ばないようにして、なんらかのクラスでラッピングしてください。


CakePHPのコントローラーへのインテグレーションテストで意図的に例外発生時のテストをするには、例外が発生しうる処理をイベントを利用してモックと差し替える方法があります。

ここでは、モックに差し替える方法として、IntegrationTestCase::controllerSpy を利用する方法を紹介します。

以下のような SomeController.php があったとして、

php

1class SomeController extends Controller 2{ 3 4 /** 5 * @var AwesomeService 6 */ 7 privete $service; 8 9 public function initialize() 10 { 11 parent::initialize(); 12 // ここで処理を行うサービスを初期化する 13 $this->service = new AwesomeService(); 14 } 15 16 public function add() 17 { 18 try { 19 $this->service->add($this->request->getData('some_param')); 20 } catch (AwesomeException $e) { 21 $this->Flash->error('実行できませんでした: ', $e->getMessage()); 22 } 23 } 24}

このaddメソッドの例外発生をテストするには、

php

1class SomeControllerTest extends IntegrationTestCase 2{ 3 public function tearDown() 4 { 5 unset($this->mockService); // テストごとに $this->mockService をクリア 6 parent::tearDown(); 7 } 8 9 public function controllerSpy($event, $controller = null) 10 { 11 parent::controllerSpy($event, $controller); 12 13 // モックが定義されていれば置き換える 14 if ($this->mockService) { 15 $this->_controller->service = $this->mockService; 16 } 17 } 18 19 public function testAdd() 20 { 21 // AwesomeServiceのモックを作成して、addメソッド呼び出し時に例外を返す 22 $this->mockService = $this->getMockBuilder(AwesomeService::class)->getMock(); 23 $this->mockService 24 ->method('add') 25 ->willThrowException(new AwesomeException('ダミーの例外')); 26 27 // リクエストを実行 28 $this->post('/some/add', ['some_param' => 'awesome']); 29 30 // 各アサーション .... 31 } 32} 33

のように、controllerSpyで呼び出し時に例外を発生するモックを注入します。なお、controllerSpyは、Controller.initilize イベントにフックされますので、それ以降で初期化さているオブジェクトについては置き換えできないことに注意してください。

投稿2019/11/29 11:58

nojimage

総合スコア959

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2019/12/02 09:02

ありがとうございます moke化のやり方、勘違いしていた部分がわかりました
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問