Thursday, April 20, 2017

Automated API testing using Codeception

I was looking for a tool for testing an API, and I remembered about my old friend Codeception, which I used for some functional testing a while ago. I need now to test an API which is receiving requests and gives responses using JSON format.


Install Codeception via Composer
$ composer require "codeception/codeception" 
Create an alias so you do not have to write the entire path every time you execute a command:
$ alias codecept='./vendor/bin/codecept'
Create an API test suite:
$ codecept generate:suite api
Edit the configuration file  tests/api.suite.yml:


class_name: ApiTester
modules:
    enabled:
        - \Helper\Api
        - REST:
            url: "http://yourwebste.com/"
            depends: PhpBrowser
            part: Json

I will be using "Cest" format:  "Cest combines scenario-driven test approach with OOP design. In case you want to group a few testing scenarios into one you should consider using Cest format."

$ codecept generate:cest api UsersTestCest
This command is creating a PHP file under tests/api/.  
Before starting to write my first tests I need to find a way to pass a username and password to all tests, as it is needed for connecting to the API.
I can add any parameters to the api.suite.yml and access them inside my test classes:
api.suite.yml


class_name: ApiTester
modules:
    enabled:
        - \Helper\Api
        - REST:
            url: "http://yourwebsite.com"
            depends: PhpBrowser
            part: Json
params:
    username: "username"
    password: "password"


 And inside tests/api/UsersTestCest.php

class UsersTestCest
{
    private $username;
    private $password;

    public function _before(ApiTester $I)
}
    {
        $config = \Codeception\Configuration::config();
        $apiSettings = \Codeception\Configuration::suiteSettings('api', $config);
        $this->username = $apiSettings['params']['username'];
        $this->password = $apiSettings['params']['password'];
    }
}


The function "_before()" will be executed as it names says, before actual test functions, and it will  initialize the variables with values from api.suite.yml file.

Let's write the first test function:


public function createUser(ApiTester $I)
    {
        $action = "create_user";
        $I->haveHttpHeader('Content-Type', 'application/json');

        $array=[
            "action"=> $action,
            "username"=> $this->username,
            "key"=> $this->password,
            "user_username"=> "new_username",
            "user_email"=> "email@company.com",
            "user_password"=> "password"
            ]
        ];
        $string = json_encode($array);
        $I->sendPOST('api.php',$string);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK); // 200
        $I->seeResponseIsJson();
        $I->seeResponseMatchesJsonType([
            'operation' => 'string',
            'status' => 'string',
        ]);
    }

The link where the request is sent with the function sendPost() is the concatenation between the URL parameter from api.suite.yml and the first parameter in sentPost() function.
You can check if the response has HTTP status 200, if it is in JSON format and if the JSON has the expected structure.
Even more you can create your own assertions. These custom functions needs to be added to the file "tests/_support/ApiTester.php".
I will add a function to verify if the "status" field from the response is equal to '1'




<?php
namespace Helper;
class will be available in $I
use Codeception\TestInterface;

class Api extends \Codeception\Module
{
    public function checkOperationStatusSuccess()
    {
        $response = $this->getModule('REST')->response;
        $array = json_decode($response, true);
        $this->assertEquals('1',$array['status'],"Operation status should be '1' for success.");
    }
}

Run all tests with this command:
codecept run api -v

Or only tests from certain class:
$ codecept run tests/api/UsersTestCest.php -v
  
The tests are executed in the alphabetic order of the classes, but you can use the annotation "@depends" to indicated that your test function depends on other one(s). 
Enjoy!