Two hands completing a test

Let me show you a data-driven test development approach for writing APIs. The code is available at this GitHub repo, which is a demo on how to build a JWT-authenticated REST API from scratch. This API runs a few CRUD operations on the UK Premier League.

The DDTD methodology that I've implemented consists in using a classical TDD cycle, kind of like this:

  • Add a test
  • Write something in the test
  • Let it fail at the beginning
  • Refactor the code of your class/library
  • Make the test pass

And so on, iterating through this process until you're happy with the class/library being implemented.

However, with a DDTD approach we use sample data stored in an external file or database -- CSV, JSON, SQL. And rather than writing unit tests in isolation we interact with the API layer through an HTTP client -- Guzzle, Mink, Symfony WebTestCase -- in a manner that could be called functional.

Don't let the terminology scare you off. With this functionalish TDD scheme there's no need to use a REST client like Postman or Insomnia when developing a new API, if you don't want to; PHPUnit will do the job instead through the HTTP client of your choice.

Here is how I have structured the testing folders:

Figure 1

Figure 1. tests folder

Figure 2

Figure 2. tests/auth folder

Figure 3

Figure 3. tests/team folder

Hopefully all this should be self-explanatory since I've just followed a convention with a clear objective to write the code systematically.

So, the tests subfolders represent the endpoints themselves as it is shown below.

Figure 4

Figure 4. tests/team/update/{id} folder

As you see, the actual tests are named like this:

  • HttpStatus200Test.php
  • HttpStatus400Test.php
  • HttpStatus401Test.php
  • HttpStatus404Test.php
  • ...

They all are very similar -- they do the exact same thing actually: read the JSON data from the file system and use it to query the API endpoints. Yep, we're just defining at development time how the API responds by using sample data!

Let's look at an example.

// tests/team/create/HttpStatus401Test.php
namespace App\Tests\Team\Create;

use App\Tests\TokenAuthenticatedWebTestCase;

class HttpStatus401Test extends TokenAuthenticatedWebTestCase
    public static function setUpBeforeClass()
        self::$accessToken = 'foo';

     * @dataProvider data
     * @test
    public function http_status_401($name, $location, $stadium, $season)
        $team = [
            'name' => $name,
            'location' => $location,
            'stadium' => $stadium,
            'season' => $season,

        $client = static::createClient();

                'HTTP_AUTHORIZATION' => 'Bearer '.self::$accessToken,
                'CONTENT_TYPE' => 'application/json',

        $this->assertEquals(401, $client->getResponse()->getStatusCode());

    public function data()
        $data = [];
        $teams = json_decode(file_get_contents(__DIR__.'/data/http_status_200.json'))->httpBody;
        foreach ($teams as $team) {
            $data[] = [

        return $data;

This one tests that a valid access token is required when creating teams, otherwise the API responds with a 401 HTTP error code (Unauthorized).

That's all folks! I hope you enjoyed today's post.

You may also be interested in...

Previous Post Next Post