cult3

How to create an Active Record style PHP SDK Part 8

Aug 27, 2014

Table of contents:

  1. Stubbing responses
  2. Writing the tests for the find and all methods
  3. Create the Findable trait
  4. Implementing the FindOne and FindAll traits
  5. Conclusion

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.

Stubbing responses

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": [
            "...",
            "..."
        ]
    }
}

Writing the tests for the find and all methods

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.

Create the Findable trait

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().

Implementing the FindOne and FindAll traits

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.

Conclusion

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.

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.