In this example we're going to call mock Guzzle client class in test environment so that we don't call external APIs while running tests.


How it works


When we use our application, AppBundle\Util\ClientHelper is used for dev and prod environments while AppBundle\Features\Mock\ClientHelperMock is used for test environment.


Config.yml


parameters:
client_helper_class: AppBundle\Util\ClientHelper

Config_test.yml


parameters:
client_helper_class: AppBundle\Features\Mock\ClientHelperMock

PostcodeService


namespace AppBundle\Service;

use AppBundle\Util\ClientHelper;
use Symfony\Component\HttpFoundation\Request;

class PostcodeService
{
private $client;
private $apiUri;

public function __construct(
ClientHelper $client,
$apiUri
) {
$this->client = $client;
$this->apiUri = $apiUri;
}

public function get($postcode)
{
return $this->client->request(Request::METHOD_GET, $this->apiUri.$postcode);
}
}

service:
app.service.postcode:
class: AppBundle\Service\PostcodeService
arguments:
- "@app.util.client_helper"
- "%postcodes_api%"

ClientHelper


namespace AppBundle\Util;

use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Psr7\Stream;

class ClientHelper
{
private $client;

public function __construct(ClientInterface $client)
{
$this->client = $client;
}

public function request($method, $uri, array $options = [])
{
$details = null;

try {
/** @var Response $response */
$response = $this->client->request($method, $uri, $options);
/** @var Stream $body */
$body = $response->getBody();
$details = $body->getContents();
} catch (ClientException $e) {
}

return $details;
}
}

services:
app.util.client_helper:
class: '%client_helper_class%'
arguments:
- '@app.util.guzzle'

app.util.guzzle:
class: GuzzleHttp\Client

ClientHelperMock


namespace AppBundle\Features\Mock;

use AppBundle\Util\ClientHelper;

class ClientHelperMock extends ClientHelper
{
public static $data;

public function request($method, $uri, array $options = [])
{
$response = [];

$position = strrpos($uri, '/');
if ($position !== false) {
$postcode = substr($uri, $position+1);

if (isset(self::$data['postcode']) && self::$data['postcode'] == $postcode) {
$response = self::$data['result'];
}
}

return json_encode($response);
}
}

Gherkin step definition


/**
* @param PyStringNode $stringNode
*
* @Given /^the Postcode API is available with data:$/
*/
public function thePostcodeApiIsAvailableWith(PyStringNode $stringNode)
{
ClientHelperMock::$data = json_decode($stringNode->getRaw(), true);
}

Scenarios


Feature: Getting postcode details.
In order to get postcode details
As a user
I should be able call call external API

Scenario: I get empty result for non-existent postcode.
Given the Postcode API is available with data:
"""
{"postcode":"POSTCODE 1","result":{"longitude":-0.11111111,"latitude":0.11111111}}
"""
When I send a "GET" request to "/postcodes/NON-EXISTENT POSTCODE"
Then the response status code should be 200
And the response should contain json:
"""
[]
"""

Scenario: I get full result for existent postcode.
Given the Postcode API is available with data:
"""
{"postcode":"POSTCODE 1","result":{"longitude":-0.11111111,"latitude":0.11111111}}
"""
When I send a "GET" request to "/postcodes/POSTCODE 1"
Then the response status code should be 200
And the response should contain json:
"""
{"longitude":-0.11111111,"latitude":0.11111111}
"""

Result


$ vendor/bin/behat --suite=app src/AppBundle/Features/Postcode.feature
Feature: Getting postcode details.
In order to get postcode details
As a user
I should be able call call external API

Scenario: I get empty result for non-existent postcode.
Given the Postcode API is available with data:
"""
{"postcode":"POSTCODE 1","result":{"longitude":-0.11111111,"latitude":0.11111111}}
"""
When I send a "GET" request to "/postcodes/NON-EXISTENT POSTCODE"
Then the response status code should be 200
And the response should contain json:
"""
[]
"""

Scenario: I get full result for existent postcode.
Given the Postcode API is available with data:
"""
{"postcode":"POSTCODE 1","result":{"longitude":-0.11111111,"latitude":0.11111111}}
"""
When I send a "GET" request to "/postcodes/POSTCODE 1"
Then the response status code should be 200
And the response should contain json:
"""
{"longitude":-0.11111111,"latitude":0.11111111}
"""

2 scenarios (2 passed)
8 steps (8 passed)
0m1.24s (30.72Mb)