Testing your REST APIs

You have already been testing your REST API after finishing each controller by making some request and expecting a response. As you might imagine, this can be handy sometimes, but it is for sure not the way to go. Testing should be automatic, and should cover as much as possible. We will have to think of a solution similar to unit testing.

In Chapter 10, Behavioral Testing, you will learn more methodologies and tools for testing an application end to end, and that will include REST APIs. However, due to the simplicity of our REST API, we can add some pretty good tests with what Laravel provides us as well. Actually, the idea is very similar to the tests that we wrote in Chapter 8, Using Existing PHP Frameworks, where we made a request to some endpoint, and expected a response. The only difference will be in the kind of assertions that we use (which can check if a JSON response is OK), and the way we perform requests.

Let's add some tests to the set of endpoints related to books. We need some books in the database in order to query them, so we will have to populate the database before each test, that is, use the setUp method. Remember that in order to leave the database clean of test data, we need to use the trait DatabaseTransactions. Add the following code to tests/BooksTest.php:

<?php

use IlluminateFoundationTestingDatabaseTransactions;
use AppBook;

class BooksTest extends TestCase {

    use DatabaseTransactions;

    private $books = [];

    public function setUp() {
        parent::setUp();

        $this->addBooks();
    }

    private function addBooks() {
        $this->books[0] = Book::create(
            [
                'isbn' => '293842983648273',
                'title' => 'Iliad',
                'author' => 'Homer',
                'stock' => 12,
                'price' => 7.40
            ]
        );
        $this->books[0]->save();
        $this->books[0] = $this->books[0]->fresh();

        $this->books[1] = Book::create(
            [
                'isbn' => '9879287342342',
                'title' => 'Odyssey',
                'author' => 'Homer',
                'stock' => 8,
                'price' => 10.60
            ]
        );
        $this->books[1]->save();
        $this->books[1] = $this->books[1]->fresh();

        $this->books[2] = Book::create(
            [
                'isbn' => '312312314235324',
                'title' => 'The Illuminati',
                'author' => 'Larry Burkett',
                'stock' => 22,
                'price' => 5.10
            ]
        );
        $this->books[2]->save();
        $this->books[2] = $this->books[2]->fresh();
    }
}

As you can see in the preceding code, we add three books to the database, and to the class property $books too. We will need them when we want to assert that a response is valid. Also note the use of the fresh method; this method synchronizes the model that we have with the content in the database. We need to do this in order to get the ID inserted in the database, since we do not know it a priori.

There is another thing we need to do before we run each test: authenticating our client. We will need to make a POST request to the access token generation endpoint sending valid credentials, and storing the access token that we receive so that it can be used in the remaining requests. You are free to choose how to provide the credentials, since there are different ways to do it. In our case, we just provide the credentials of a client test that we know exists in the database, but you might prefer to insert that client into the database each time. Update the test with the following code:

<?php

use IlluminateFoundationTestingDatabaseTransactions;
use AppBook;

class BooksTest extends TestCase {

    use DatabaseTransactions;

    private $books = [];
    private $accessToken;

    public function setUp() {
        parent::setUp();

        $this->addBooks();
        $this->authenticate();
    }

    //...

    private function authenticate() {
        $this->post(
            'oauth/access_token',
            [
                'client_id' => 'iTh4Mzl0EAPn90sK4EhAmVEXS',
                'client_secret' => 'PfoWM9yq4Bh6rhr8oDDsNZM',
                'grant_type' => 'client_credentials'
            ]
        );
        $response = json_decode(
            $this->response->getContent(), true
        );
        $this->accessToken = $response['access_token'];
    }
}

In the preceding code, we use the post method in order to send a POST request. This method accepts a string with the endpoint, and an array with the parameters to be included. After making a request, Laravel saves the response object into the $response property. We can JSON-decode it, and extract the access token that we need.

It is time to add some tests. Let's start with an easy one: requesting a book given an ID. The ID is used to make the GET requests with the ID of the book (do not forget the access token), and check if the response matches the expected one. Remember that we have the $books array already, so it will be pretty easy to perform these checks.

We will be using two assertions: seeJson, which compares the received JSON response with the one that we provide, and assertResponseOk, which you already know from previous tests—it just checks that the response has a 200 status code. Add this test to the class:

public function testGetBook() {
    $expectedResponse = [
        'book' => json_decode($this->books[1], true)
    ];
    $url = 'books/' . $this->books[1]->id
        . '?' . $this->getCredentials();

    $this->get($url)
        ->seeJson($expectedResponse)
        ->assertResponseOk();
}

private function getCredentials(): string {
    return 'grant_access=client_credentials&access_token='
        . $this->accessToken;
}

We use the get method instead of post, since this is a GET request. Also note that we use the getCredentials helper, since we will have to use it in each test. To see another example, let's add a test that checks the response when requesting the books that contain the given title:

public function testGetBooksByTitle() {
    $expectedResponse = [
        'books' => [
            json_decode($this->books[0], true),
            json_decode($this->books[2], true)
        ]
    ];

    $url = 'books/?title=Il&' . $this->getCredentials();
    $this->get($url)
        ->seeJson($expectedResponse)
        ->assertResponseOk();
}

The preceding test is pretty much the same as the previous one, isn't it? The only changes are the endpoint and the expected response. Well, the remaining tests will all follow the same pattern, since so far, we can only fetch books and filter them.

To see something different, let's check how to test an endpoint that creates resources. There are different options, one of them being to first make the request, and then going to the database to check that the resource has been created. Another option, the one that we prefer, is to first send the request that creates the resource, and then, with the information in the response, send a request to fetch the newly created resource. This is preferable, since we are testing only the REST API, and we do not need to know the specific schema that the database is using. Also, if the REST API changes its database, the tests will keep passing—and they should—since we test through the interface only.

One good example could be borrowing a book. The test should first send a POST in order to borrow the book, specifying the book ID, then extract the borrowed book ID from the response, and finally send a GET request asking for that borrowed book. To save time, you can add the following test to the already existing tests/BooksTest.php:

public function testBorrowBook() {
    $params = ['book-id' => $this->books[1]->id];
    $params = array_merge($params, $this->postCredentials());

    $this->post('borrowed-books', $params)
        ->seeJsonContains(['book_id' => $this->books[1]->id])
        ->assertResponseOk();

    $response = json_decode($this->response->getContent(), true);

    $url = 'borrowed-books' . '?' . $this->getCredentials();
    $this->get($url)
        ->seeJsonContains(['id' => $response['borrowed-book']['id']])
        ->assertResponseOk();
}

private function postCredentials(): array {
    return [
        'grant_access' => 'client_credentials',
        'access_token' => $this->accessToken
    ];
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.239.44