How to write Unit Tests for SlimPHP 4

How to write Unit Tests for SlimPHP 4

I am motivated to write this article because more comprehensive documentation and examples are needed to write unit tests in SlimPHP 4, unlike its predecessor, SlimPHP 3, which provided ample resources on this subject. It is crucial to implement unit testing in all applications, and for this article, I will focus solely on discussing the process of writing unit tests in SlimPHP 4.

Setting up your unit testing

We'll need to change the composer.json file by adding the following components to move forward.

This will facilitate the required functionality and ensure the smooth execution of subsequent processes.

[
    "require-dev": {
        "phpunit/phpunit": "^9.5",
        "robtimus/multipart": "1.*"
    },
    "autoload-dev": {
        "psr-4": {
            "Tests\\": "tests/"
        }
    },
    "scripts": {
        "start": "php -S localhost:8888 -t public",
        "test": "phpunit --configuration phpunit.xml"
    }
]

Next, let's create a phpunit.xml configuration file. This step is essential to ensure that PHPUnit can adequately execute the unit tests in our project.

<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" backupGlobals="true" backupStaticAttributes="false" beStrictAboutTestsThatDoNotTestAnything="true" beStrictAboutChangesToGlobalState="false" beStrictAboutOutputDuringTests="true" colors="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" bootstrap="tests/autoload.php">
  <coverage processUncoveredFiles="true">
    <include>
      <directory suffix=".php">src/</directory>
    </include>
  </coverage>
  <testsuites>
    <testsuite name="RESTAPI Test Suite">
        <file>./tests/DummyTest.php</file>
    </testsuite>
  </testsuites>
</phpunit>

After successfully configuring PHPUnit to execute tests in a specific order, we will implement a Dummy Test. This essential step in our testing process will allow us to verify that the PHPUnit configuration has been accurately set up.

By doing so, we can ensure that all subsequent tests are executed in the desired order and that any potential issues with the configuration are promptly detected and resolved. Such a proactive approach will enable us to maintain a high-quality assurance standard in our testing procedures.

Finally, we need to create a tests directory and make a DummyTest.

mkdir tests
touch tests/DummyTest.php

Then paste the following code into your DummyTest

<?php

use PHPUnit\Framework\TestCase;

/**
* Trivial dummy test used to confirm test configuration working.
*/
class DummyTest extends TestCase
{
    /**
    * Trivial dummy test guaranteed to succeed.
    */
    public function testAssertEquals()
    {
        $this->assertEquals('abc', 'abc');
    }
}

We also need to create a new file named autoload.php and paste the following code into it.

<?php

require_once __DIR__.'/../vendor/autoload.php';

Running your first test

Now that all the setup is done, we can install PHPUnit and start our first test.

Run the following commands in your terminal to test that everything is set up and working correctly.

composer update
composer test

You should receive a message saying 1 test was completed.

Building the first real unit test

Now you have PHPUnit set up and working. We can start by creating some tests. Instead of making an application, let's use this tutorial. I found it on a simple Google search that builds an extremely simple REST API.

There's a Route to get all Customers, so to make that work first, I created a TestCase class that extends the PHPUnit Frameworks TestCase.

That way, I could add some Database Commands to the Tests. Since the guide didn't use a Container or Actions for each route, we will have to do some hacky coding to get the tests to work.

First, create a file tests/TestCase.php and paste the following into the file.

<?php
namespace Tests;

use PHPUnit\Framework\TestCase as PHPUNIT_TestCase;
use Slim\Factory\AppFactory;
use Slim\Psr7\Factory\RequestFactory;
use Slim\Psr7\Factory\ResponseFactory;
use Slim\Psr7\Factory\ServerRequestFactory;
use Slim\Psr7\Response;
use Slim\Psr7\Stream;
use App\Models\Db;

Class TestCase extends PHPUNIT_TestCase
{
  public $customer;
  public $lastID;

  public function getCustomer()
  {
    $SQL = "SELECT * FROM customers ORDER BY id DESC LIMIT 1";
    $db = new Db();
    $conn = $db->connect();
    $stmt = $conn->query($SQL);
    $customer = $stmt->fetch(\PDO::FETCH_NAMED);
    $this->customer = (object) $customer;
    $this->lastID = $this->customer->id;
    unset($db);
    return $this->user;
  }

  // ------------------------------------------------------------------------

  public function getTotalCustomers()
  {
    $db = new Db();
    $conn = $db->connect();
    $sql = "SELECT count(id) AS count FROM customers GROUP BY id";
    $stmt = $conn->prepare($sql);
    $customers = $stmt->execute();
    $customers = $stmt->fetchColumn();
    return $customers;
  }

  // ------------------------------------------------------------------------

  public function getLastCustomerID()
  {
    if (!is_int($this->lastID) OR !is_object($this->user)) {
      $this->getCustomer();
    }

    return $this->customer->id;
  }

}

There are better methods of doing this, but this is the best method I found that worked without using an Application Container PHP-DI and without using Actions instead of writing the code directly into the route's anonymous functions.

Finally, let's write a simple test to verify that the GET /customers route tests correctly.

Create a file tests/GetCustomersTest.php and paste the following into it.


<?php

use PHPUnit\Framework\TestCase;
use Slim\Factory\AppFactory;
use Slim\Psr7\Factory\ServerRequestFactory;
use Slim\Psr7\Response;
use Slim\Psr7\Stream;

class GetcustomersTest extends TestCase {

  public function testGetRoute() {

    // Set up the app
    $app = AppFactory::create();

    // Add the route to be tested
    $app->get('/customers-data/all', function (Request $request, Response $response) {
      $sql = "SELECT * FROM customers";

      try {
        $db = new Db();
        $conn = $db->connect();
        $stmt = $conn->query($sql);
        $customers = $stmt->fetchAll(PDO::FETCH_OBJ);
        $db = null;

        $response->getBody()->write(json_encode($customers));
        return $response
        ->withHeader('content-type', 'application/json')
        ->withStatus(200);
      } catch (PDOException $e) {
        $error = array(
          "message" => $e->getMessage()
        );

        $response->getBody()->write(json_encode($error));
        return $response
        ->withHeader('content-type', 'application/json')
        ->withStatus(500);
      }
    });

    // Create a new GET request to the route
    $request = (new ServerRequestFactory())->createServerRequest('GET', '/customers/all');

    // Invoke the application
    $response = $app->handle($request);

    // Assert that the response status code is 200
    $this->assertEquals(200, $response->getStatusCode());

    $existing_customers = $this->getTotalCustomers();

    $customers = (array) json_decode($response->getBody());
    $count     = count($customers);

    $this->assertIsArray($body);
    $this->assertCount($count, $existing_customers, 'The count of the existing customers does not match the count of the customers returned');
  }
}

And to make this test run, edit phpunit.xml and add the test case to the Test Suite by adding this line.

        <file>./tests/GetCustomersTest.php</file>

In summary, this is not the correct way to write tests like these, but the guide I found online didn't use a Container or Actions. And I needed something very similar but couldn't find any helpful information online, so I wrote this article.

Maybe I will write another article showing how to correctly write the REST API with a Container and Actions. If anybody is interested in it, please leave a comment.

Did you find this article valuable?

Support Shawn Crigger by becoming a sponsor. Any amount is appreciated!