Aug 27, 2014
Table of contents:
Last week we looked at setting up the foundation of being able to query the CapsuleCRM API.
First we separated the querying functionality into it’s own directory and split each querying action into it’s own trait.
Next we created the convention of inferring the API endpoint, but also allowing local configuration to override this convention on a case-by-case basis.
In today’s tutorial we’ll be looking at actually querying the API using the HTTP Connection object and returning a response in the form of JSON.
As I mentioned a couple of weeks ago, whenever we write unit tests for an SDK, we never want to actually hit the API. As far as we are concerned, the API is none of our business and so we only have to write tests for our code.
An API is a contract that explicitly outlines what requests it expects and what responses you should expect. Usually API documentation will have sample request and responses in the form of XML or JSON snippets.
We can use these provided response snippets to mock the response from the API, and therefore test our code, without ever having to actually hit the API endpoint.
In today’s tutorial we are simply writing tests to define the basic building blocks of finding one or finding all the entities from the API resource. However in a future tutorial we will also look at how we can use the provided API response snippets to test out code.
For now, create a new directory under tests
called stubs
. In this directory we’re going to keep the stub JSON responses that we will use to mock a response from the API.
Create a file called stub.json
and copy the following:
{
"stub": {
"id": "123"
}
}
Next create a file called stubs.json
and copy the following:
{
"stubs": {
"stub": [
"...",
"..."
]
}
}
Last week we created two traits for adding the find()
and the all()
methods to our model objects. This will allow the model object to search for a single entity or many entities from the API.
At the minute we’re still looking to test this underlying foundation of the model object. We’ll eventually be testing each individual model instance, but for now we can just test that these methods are working correctly.
Open up the QueryingTest
file and update your setUp()
method to look like this:
public function setUp()
{
$this->connection = m::mock('PhilipBrown\CapsuleCRM\Connection');
$this->message = m::mock('Guzzle\Http\Message\Response');
$this->model = new ModelStub($this->connection);
}
I’ve updated the $connection
instance to $this->connection
so I can mock expected method calls with each test.
I’ve also added $this->message
as a mock of Guzzle’s Response
class. This means we can mock the response of the API.
Next add the following two tests:
public function testFindOneReturnsOneEntity()
{
$stub = file_get_contents(dirname(__FILE__).'/stubs/stub.json');
$this->message->shouldReceive('json')->andReturn(json_decode($stub, true));
$this->connection->shouldReceive('get')->andReturn($this->message);
$response = $this->model->find(1);
$this->assertTrue(isset($response['stub']));
}
public function testFindAllReturnsAllEntities()
{
$stub = file_get_contents(dirname(__FILE__).'/stubs/stubs.json');
$this->message->shouldReceive('json')->andReturn(json_decode($stub, true));
$this->connection->shouldReceive('get')->andReturn($this->message);
$response = $this->model->all();
$this->assertTrue(isset($response['stubs']));
}
These two tests are simply asserting that the method works as expected.
First we get the stub.json
file and set it as the return value of $this->message
.
Next we set the $this->connection
to return the $this->message
response.
Finally we get the $response
from the all()
method call on the $this->model
instance.
And finally we assert the response is correct.
In all honest this isn’t testing much, but it does outline how the package should be used. The real valuable tests will be for each model instance, but it doesn’t hurt to have these tests also.
To add the ability to query to a model class we need to add the following three traits:
Configuration
FindOne
FindAll
Most of the models are going to require all of these traits as it’s only a couple that don’t need to be able to find a single entity as well as all entities.
To prevent repetition we can create a new Findable
trait that will allow us to include these three traits all in one go:
<?php namespace PhilipBrown\CapsuleCRM\Querying;
trait Findable
{
use Configuration;
use FindAll;
use FindOne;
}
Now we can add the Findable
trait to the ModelStub
to give it the methods find()
and all()
.
Finally we can implement the FineOne
and FindAll
traits.
The FindOne
trait looks like this:
<?php namespace PhilipBrown\CapsuleCRM\Querying;
trait FindOne
{
/**
* Find a single entity by it's id
*
* @param int $id
* @return PhilipBrown\CapsuleCRM\Entity
*/
public function find($id)
{
$endpoint = "/api/" . $this->queryableOptions()->singular() . "/" . $id;
$response = $this->connection->get($endpoint);
return $response->json();
}
}
And the FindAll
trait looks like this:
<?php namespace PhilipBrown\CapsuleCRM\Querying;
trait FindAll
{
/**
* Return all entities of the current model
*
* @param array $params
* @return array
*/
public function all($params = [])
{
$endpoint = "/api/" . $this->queryableOptions()->plural();
$response = $this->connection->get($endpoint, $params);
return $response->json();
}
}
Both of these traits simply build up the $endpoint
and then pass it to the get()
method on the Connection
instance.
This will return a Guzzle Response
object that has a json()
method available. Calling this method will return the body of the response.
Now if you run those tests from earlier you should see them all pass.
In today’s tutorial we’ve laid the foundation for querying the API. The find()
and all()
methods can be very simple because we’ve moved the responsibility of setting up the request to specific classes.
The code at this point is now capable of querying the CapsuleCRM API and returning a JSON response. This is where a lot of API SDK’s leave it.
However, because we are building an Active Record style SDK, we need a way of transforming those JSON responses into actual model objects.
Over the next couple of weeks we will be looking at how to take these raw JSON responses and transform them into real model objects that embody the characteristics of the Active Record Pattern.