Tuesday, 10 April 2018

Api testing with codeception and Yii2 http client


Testing API integrations has been one of those things I could not find an elegant solution for. This was before I found yii2's http client. With the use of their "Transports" we can fake API calls for testing without actually hitting the API. By faking responses we can test how our applications handles different responses.

The main two advantages we get from this is our test are much faster, and we are not using are API quota for the service we are using. We are also testing how our applications handles the response from the API not the API itself. One thing to watch with this approach is that if the endpoints that you are hitting change, your test will not catch that. So it may be a good idea to have one test that hits the API, saves the results that all the other test use.

We can set our own transport in the app config if the environment is not production. You can always use 'YII_ENV_TEST' if you only want to use the transport in a testing environment.

$config = [
  // ...
  'components' => [
	'curl' => [
	  'class' => 'yii\httpclient\Client'
	]
  ]
];

if (!YII_ENV_PROD) {
  $config['components']['curl']['transport'] = 'tests\_support\TestTransport';
}

return $config;

Now every time we use the http client in the environment specified, we will be using the test transport that looks like this.

<?php

namespace tests\_support;

use yii\helpers\Json;

class TestTransport extends \yii\httpclient\Transport
{
  public function send($request)
  {
    switch ($request->toString()) {
      case 'GET https://myurl.com':
        $responseContent = $this->getGoodData();
        break;
      default:
        $responseContent = $this->get404();
    }

    return $request->client->createResponse(
      $responseContent,
      $this->getHeaders()
    );
  }

  protected function getHeaders()
  {
    $dt = new \DateTime('UTC');
    $date = $dt->format('D, d M Y H:i:s \G\M\T');

    $dt->add(new \DateInterval('P1D'));
    $expires = $dt->format('D, d M Y H:i:s \G\M\T');

    return [
      'HTTP/1.0 200 OK',
      'Content-Type: application/json; charset=UTF-8',
      'Date: ' . $date,
      'Expires: ' . $expires,
      'Cache-Control: public, max-age=86400',
      'Access-Control-Allow-Origin: *',
      'X-Codecept-Test: TRUE',
      '"Accept-Ranges: none',
      '"Vary: Accept-Language,Accept-Encoding'
    ];
  }

  public function get404()
  {
    return Json::encode([
      'status' => 'Not Found',
      'code' => 404
    ]);
  }

  public function getGoodData()
  {
    return Json::encode([
      'status' => 'Found',
      'code' => 200,
      'data' => [
        'one' => 'two'
      ]
    ]);
  }
}

Now we can hit the API without using the quota and speeding up the tests.

$response = Yii::$app->curl->get('https://myurl.com')->send();