I want to test the processing when an error occurs when a request is sent to an external API

Error message

A registration request was sent to the API side with an invalid value in the test controller
Then the API returned an error and the test failed


Payjp \ Error \ InvalidRequest: No such token:

if (! $payjpCustomer->save ()) {
            $result = 1;
        $this->assertEquals (1, $result);
source code:(tested controller)
try {
          $payjpCustomer = \ Payjp \ Customer :: retrieve ($payjpCustomerId);
          // Delete registered card information
          $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 ("Registration failed. Please try again.");

I changed the test code as follows
Although it is natural, the error written before that is still returned from the API side

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

In the original controller, whether it is communication with the API side or the timing of saving the result in the DB, if any problem occurs, error message etc. will be displayed with tyr catch

When you try to write test code for a controller that performs such processing in cooperation with an external service, do you write tyr catch in the test code to see the behavior?

Or, if an exception occurs when an invalid value is requested to the controller, it can be verified by a unit test
If I want to change the behavior by throwing a request to an external API etc. and changing the behavior, you can test directly by debugging with that controller or operating from the front side

Is this used properly?

Supplemental information (FW/tool version etc.)

cakephp 3.5
API payjp api

  • Answer # 1

    As a premise, do not call the process for connecting to the external API directly from the controller, and wrap it in some class.

    There is a way to replace the processing that can cause an exception with a mock using an event to intentionally test when an exception occurs in the integration test to CakePHP controller.

    Here, we will introduce how to use IntegrationTestCase :: controllerSpy as a way to replace mock.

    Assuming you have SomeController.php like below,

    class SomeController extends Controller
        / **
         * @var AwesomeService
         * /
        privete $service;
        public function initialize ()
            parent :: initialize ();
            // initialize the service to process here
            $this->service = new AwesomeService ();
        public function add ()
            try {
                $this->service->add ($this->request->getData ('some_param'));
            } catch (AwesomeException $e) {
                $this->Flash->error ('Could not execute:', $e->getMessage ());

    To test for exceptions in this add method,

    class SomeControllerTest extends IntegrationTestCase
        public function tearDown ()
            unset ($this->mockService);// clear $this->mockService for each test
            parent :: tearDown ();
        public function controllerSpy ($event, $controller = null)
            parent :: controllerSpy ($event, $controller);
            // replace mock if defined
            if ($this->mockService) {
                $this->_ controller->service = $this->mockService;
        public function testAdd ()
            // Create an AwesomeService mock and return an exception when calling the add method
            $this->mockService = $this->getMockBuilder (AwesomeService :: class)->getMock ();
                ->method ('add')
                ->willThrowException (new AwesomeException ('dummy exception'));
            // execute request
            $this->post ('/ some/add', ['some_param' =>'awesome']);
            // each assertion ....
    Inject a mock that raises an exception when called in controllerSpy like

    . Note that controllerSpy is hooked to theController.initilizeevent, so objects initialized after that cannot be replaced.