Assume that your application is an API that relies on in_memory security providers and obviously needs Basic Auth for authentication. To test APIs, we can use Chrome Postman extension without hassle however we're going to do it programatically with behat.


Rules



Configuration


Guzzle 6.1.1 is way slower than 4.2.3 so I suggest you to use 4.2.3 instead.


Composer.json


{
"require-dev": {
"behat/behat": "3.0.15",
"behat/symfony2-extension": "2.1.0",
"behat/mink": "1.7.0",
"behat/mink-extension": "2.1.0",
"behat/mink-browserkit-driver": "1.3.0",
"guzzlehttp/guzzle": "6.1.1"
},
}

Behat.yml


default:
extensions:
Behat\Symfony2Extension: ~
Behat\MinkExtension:
base_url: http://webservice.local/app_test.php/
sessions:
symfony2:
symfony2: ~
suites:
api:
type: symfony_bundle
bundle: ApplicationApiBundle
mink_session: symfony2
contexts:
- Application\ApiBundle\Features\Context\FeatureContext:
baseUrl: http://webservice.local/app_test.php/

FeatureContext


You can use Chrome Postman to generate password hashes below.


namespace Application\ApiBundle\Features\Context;

use Behat\Gherkin\Node\PyStringNode;
use Behat\MinkExtension\Context\MinkContext;
use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Response;

class FeatureContext extends MinkContext
{
private $baseUrl;
private $client;
private $headers;
private $errorCode;
private $authorisation = [
'student' => 'Basic c3R1ZGVudDpzdHVkZW50',
'lecturer' => 'Basic bGVjdHVyZXI6bGVjdHVyZXI=',
'admin' => 'Basic YWRtaW46YWRtaW4=',
];
/** @var Response $response */
private $response;

public function __construct($baseUrl)
{
$this->baseUrl = $baseUrl;
$this->client = new Client();
}

/**
* @param string $user
*
* @Then /^I am authorised as "([^"]*)"$/
*/
public function iAmAuthorisedAs($user)
{
$this->headers['Authorization'] = $this->authorisation[$user];
}

/**
* @param string $key
* @param string $value
*
* @Then /^I set "([^"]*)" header as "([^"]*)"$/
*/
public function iSetHeaderAs($key, $value)
{
$this->headers[$key] = $value;
}

/**
* @param string $method
* @param string $uri
* @param null|PyStringNode $value
*
* @When /^I send a "([^"]*)" request to "([^"]*)"$/
* @When /^I send a "([^"]*)" request to "([^"]*)" containing:$/
*/
public function iSendRequestTo($method, $uri, PyStringNode $value = null)
{
try {
$this->response = $this->client->request(
$method,
$this->baseUrl.$uri,
[
'headers' => $this->headers,
'body' => $value
]
);
} catch (Exception $e) {
$this->errorCode = $e->getCode();
}
}

/**
* @param int $code
*
* @When /^the response status code should be "([^"]*)"$/
*
* @throws Exception
*/
public function theResponseStatusCodeShouldBe($code)
{
if ($this->errorCode && $this->errorCode != $code) {
throw new Exception(
sprintf('Expected status code %s but got %s', $code, $this->errorCode)
);
}
}

/**
* @param PyStringNode $value
*
* @When /^the response should contain:$/
*
* @throws Exception
*/
public function theResponseShouldContain(PyStringNode $value)
{
if ($value->getRaw() != json_decode($this->response->getBody())) {
throw new Exception(
sprintf('Response contains:'.PHP_EOL.'%s', json_decode($this->response->getBody()))
);
}
}

/**
* @param string $header
* @param string $value
*
* @When /^the response header "([^"]*)" should be "([^"]*)"$/
*
* @throws Exception
*/
public function theResponseHeaderShouldBe($header, $value)
{
$result = $this->response->getHeader($header);
if (!$this->response || !is_array($result) || !in_array($value, $result)) {
throw new Exception('Response header could not be found.');
}
}
}

If you decide to use Guzzle 4.2.3, use code below for iSendRequestTo method instead.


use GuzzleHttp\Client;
use GuzzleHttp\Exception\BadResponseException;
use GuzzleHttp\Stream\Stream;

$client = new Client();
$request = $client->createRequest('POST', 'http://www.whatever.com/api/student',);
$request->addHeader('Authorization', 'Basic 23erfgh678iED6tyuh);
$request->addHeader('Content-Type', 'application/json');
$request->setBody(Stream::factory($postData));
$result = $client->send($request);

Test scenarios


Tests below could be run with commands below.


# Run all tests
bin/behat --suite=api

# Run only StudentControls.feature test scenarios
bin/behat --suite=api @ApplicationApiBundle/StudentControls.feature

# Run single StudentControls.feature test scenario which is on line 12
bin/behat --suite=api @ApplicationApiBundle/StudentControls.feature:12

Student


# src/Application/ApiBundle/Features/StudentControls.feature

Feature: Testing Student account
In order to test Student account
As a Student user
I should be able to send requests

Scenario: I get error when sending invalid content headers
Given I am authorised as "student"
And I set "Content-Type" header as "text/plain"
When I send a "GET" request to "api/student"
Then the response status code should be "400"

Scenario: I cannot see lecturer
Given I am authorised as "student"
And I set "Content-Type" header as "application/json"
When I send a "GET" request to "api/lecturer"
Then the response status code should be "403"

Scenario: I can successfully see students
Given I am authorised as "student"
And I set "Content-Type" header as "application/json"
When I send a "GET" request to "api/student"
Then the response status code should be "200"
And the response header "Content-Type" should be "application/json"
And the response should contain:
"""
Hello ROLE_STUDENT user
"""

Lecturer


# src/Application/ApiBundle/Features/LecturerControls.feature

Feature: Testing Lecturer account
In order to test Lecturer account
As an Lecturer user
I should be able to send requests to carry out lecturer works

Scenario: I get error when sending invalid content headers
Given I am authorised as "lecturer"
And I set "Content-Type" header as "text/plain"
When I send a "GET" request to "api/student"
Then the response status code should be "400"

Scenario: I cannot see admins
Given I am authorised as "student"
And I set "Content-Type" header as "application/json"
When I send a "GET" request to "api/admin"
Then the response status code should be "403"

Scenario: I can successfully see lecturers
Given I am authorised as "lecturer"
And I set "Content-Type" header as "application/json"
When I send a "GET" request to "api/lecturer"
Then the response status code should be "200"
And the response header "Content-Type" should be "application/json"
And the response should contain:
"""
Hello ROLE_LECTURER user
"""

Scenario: I can successfully create a new student
Given I am authorised as "lecturer"
And I set "Content-Type" header as "application/json"
When I send a "POST" request to "api/student" containing:
"""
{
"name": "Hello",
"surname": "World"
}
"""
Then the response status code should be "200"
And the response header "Content-Type" should be "application/json"
And the response should contain:
"""
{
"name": "Hello",
"surname": "World"
}
"""

Admin


# src/Application/ApiBundle/Features/AdminControls.feature

Feature: Testing Admin account
In order to test Admin account
As an Admin user
I should be able to send requests to carry out admin works

Scenario: I get error when sending invalid content headers
Given I am authorised as "admin"
And I set "Content-Type" header as "text/plain"
When I send a "GET" request to "api/admin"
Then the response status code should be "400"

Scenario: I can successfully send a valid request
Given I am authorised as "lecturer"
And I set "Content-Type" header as "application/json"
When I send a "GET" request to "api/lecturer"
Then the response status code should be "200"
And the response header "Content-Type" should be "application/json"
And the response should contain:
"""
Hello ROLE_LECTURER user
"""

Scenario: I can successfully create a new student
Given I am authorised as "lecturer"
And I set "Content-Type" header as "application/json"
When I send a "POST" request to "api/student" containing:
"""
{
"name": "Hello",
"surname": "World"
}
"""
Then the response status code should be "200"
And the response header "Content-Type" should be "application/json"
And the response should contain:
"""
{
"name": "Hello",
"surname": "World"
}
"""

Scenario: I can successfully create a new lecturer
Given I am authorised as "admin"
And I set "Content-Type" header as "application/json"
When I send a "POST" request to "api/lecturer" containing:
"""
{
"name": "Hello",
"surname": "World"
}
"""
Then the response status code should be "200"
And the response header "Content-Type" should be "application/json"
And the response should contain:
"""
{
"name": "Hello",
"surname": "World"
}
"""

Scenario: I can successfully create a new admin
Given I am authorised as "admin"
And I set "Content-Type" header as "application/json"
When I send a "POST" request to "api/admin" containing:
"""
{
"name": "Hello",
"surname": "World"
}
"""
Then the response status code should be "200"
And the response header "Content-Type" should be "application/json"
And the response should contain:
"""
{
"name": "Hello",
"surname": "World"
}
"""