How To Use PHPUnit With Slim Framework 3

Download as pdf or txt
Download as pdf or txt
You are on page 1of 11

How to Use PHPUnit With Slim Framework 3.

md 1/8/2023

Writing testable API apps in Slim framework


Editor’s Note: This is a repost of a Medium post by Oyebanji Jacob, an Andela developer who’s currently using
Node, React and Redux to build out an internal application for engineering team management. Oyebanji is
especially interested in big data and code refactoring, and when he’s not coding, you can find him playing or
watching football.

Original POST

Slim is an awesome and fast framework for building APIs in PHP.

In order to fulfill one of the requirements for becoming a PHP web developer at Andela, we are required to
write an API service for emojis using Slim framework. After reading a lot of blogs and going through the
official documentation on the website, I set out to build the app as required.

But there was a problem: I needed to be able to test my app and all the tutorials I’ve read did not introduce
me to testing Slim apps. When I googled for answers on how to test Slim apps, I found hints and approaches
for writing testable apps, but nothing that was detailed enough for me to understand how to test my unbuilt
app.

Note: Writing testable apps with good test cases is a core requirement at Andela. Your App is not good
for review if you have not written tests.

Below, I’ll demonstrate how you can test your apps written using Slim framework using a very simple example.

In this tutorial, we will be writing an API that sends simple responses to various requests on a Todo object.

App Setup
Make and change our current directory to our Todo API folder

mkdir SlimTodo && cd SlimTodo

Create a composer file for the project For this project, this is the content our base composer file.

{
"name": "pyjac/slim-todo",
"authors": [
{
"name": "Pyjac",
"email": "pyjac@pyjac.com"
}
],
"require": {}
}

1 / 11
How to Use PHPUnit With Slim Framework 3.md 1/8/2023

Install Slim We install slim by running

composer require slim/slim "^3.0"

This installs composer and its dependencies and inside the vendor folder and updates our composer.json file
to include slim as a dependency for the project.

Create public/index.php file

Following best practises, we create a folder, public, a simple file, index.php, to house our setup code.

<?php
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
require '../vendor/autoload.php';
$app = new \Slim\App;
$app->get('/', function (Request $request, Response $response) {
$response->getBody()->write("Hello, Todo");
return $response;
});
$app->run();

The above is just a simple route definition for the / or index route.

To see this in action, from the SlimTodo folder, run

php -S 127.0.0.1:8080 -t public

Then visit http://127.0.0.1:8080 on your browser.

You should see “Hello, Todo” as a response.

Good!!!

Routes
Like I said earlier, this project is meant to create a very Todo API with simple responses and write basic tests
for the endpoints

Here are the endpoints and the responses.

Request Type: GET Endpoint: /todo Status: 200 Response: “Hello, Todo” Request Type: GET Endpoint:
/todo/:id Response status: 200 or 404 Response: “Todo :id” or “Todo Not Found” Request Type: POST, PUT,
PATCH Endpoint: /todo/:id Response status: 200 or 404 Response: “Todo :id, updated successfully” or “Todo

2 / 11
How to Use PHPUnit With Slim Framework 3.md 1/8/2023

Not Found” Request Type: DELETE Endpoint: /todo/:id Response status: 200 or 404 Response: “Todo :id,
deleted successfully” or “Todo Not Found”

Let’s update our index.php to include these routes and responses.

<?php

use \Psr\Http\Message\ServerRequestInterface as Request;


use \Psr\Http\Message\ResponseInterface as Response;

require '../vendor/autoload.php';

$app = new \Slim\App;

function todoIdValid($id)
{
return (int)$id && $id > 0 && $id <= 10;
}

$app->get('/', function (Request $request, Response $response) {


$response->getBody()->write("Hello, Todo");
return $response;
});

$app->group('/todo', function () {
$this->map(['GET'], '', function (Request $request, Response $response) {
return $response->withJson(['message' => 'Hello, Todo']);
});

$this->get('/{id}', function (Request $request, Response $response, $args) {


if (todoIdValid($args['id'])) {
return $response->withJson(['message' => "Todo " . $args['id']]);
}
return $response->withJson(['message' => 'Todo Not Found'], 404);
});

$this->map(['POST', 'PUT', 'PATCH'], '/{id}', function (Request $request,


Response $response, $args) {
if (todoIdValid($args['id'])) {
return $response->withJson(['message' => "Todo " . $args['id'] . "
updated successfully"]);
}
return $response->withJson(['message' => 'Todo Not Found'], 404);
});

$this->delete('/{id}', function (Request $request, Response $response, $args)


{
if (todoIdValid($args['id'])) {
return $response->withJson(['message' => "Todo " . $args['id'] . "
deleted successfully"]);
}
return $response->withJson(['message' => 'Todo Not Found'], 404);

3 / 11
How to Use PHPUnit With Slim Framework 3.md 1/8/2023

});
});

$app->run();

Setup for Testing


First, we include PHPUnit as the a development dependency in our app.

To do that, we update our composer.json file.

{
"name": "pyjac/slim-todo",
"authors": [
{
"name": "Pyjac",
"email": "oyebanji.jacob@andela.com"
}
],
"require": {
"slim/slim": "^3.0"
},
"require-dev": {
"phpunit/phpunit": "5.4.*"
}
}

Then, we run

composer update

We’ll be creating a folder, test, to contain our tests.

mkdir test

In the test directory, create a new file named TodoTest.php and copy this as its content

<?php

class TodoTest extends PHPUnit_Framework_TestCase


{
public function testTodoGet()
{
$this->assertTrue(true);
4 / 11
How to Use PHPUnit With Slim Framework 3.md 1/8/2023

}
}

Let’s run PHPUnit against TodoTest.php to ensure we have the correct setup

./vendor/bin/phpunit ./test/TodoTest.php

You should see something related to this as output

PHPUnit 5.4.8 by Sebastian Bergmann and contributors.


. 1 / 1 (100%)
Time: 45 ms, Memory: 3.25MB
OK (1 test, 1 assertion)

Our testing setup is good!!!

Testing our API endpoints


We now have our test suite ready but we have an issue.

How do we test the app?

This was the first question I pondered over while setting out for my project.

But there is an issue with our current app setup. For our app to be testable we need to get the instance of the
app before we call the run method which bootstraps the app for web requests.

Let’s refactor our index.php file.

First, let’s create a new folder, src, to house our app class.

mkdir src

Secondly, we create a new file, App.php, in src directory and move some part of our index.php code into it.

<?php

namespace Pyjac\TodoAPI;

use \Psr\Http\Message\ServerRequestInterface as Request;


use \Psr\Http\Message\ResponseInterface as Response;

class App
{

5 / 11
How to Use PHPUnit With Slim Framework 3.md 1/8/2023

/**
* Stores an instance of the Slim application.
*
* @var \Slim\App
*/
private $app;

public function __construct()


{
$app = new \Slim\App;

$app->get('/', function (Request $request, Response $response) {


$response->getBody()->write("Hello, Todo");
return $response;
});

$app->group('/todo', function () {
$todoIdValid = function ($id) {
return (int)$id && $id > 0 && $id <= 10;
};

$this->map(['GET'], '', function (Request $request, Response


$response) {
return $response->withJson(['message' => 'Hello, Todo']);
});

$this->get('/{id}', function (Request $request, Response $response,


$args) use ($todoIdValid) {
if ($todoIdValid($args['id'])) {
return $response->withJson(['message' => "Todo " .
$args['id']]);
}

return $response->withJson(['message' => 'Todo Not Found'], 404);


});

$this->map(['POST', 'PUT', 'PATCH'], '/{id}', function (Request


$request, Response $response, $args) use ($todoIdValid) {
if ($todoIdValid($args['id'])) {
return $response->withJson(['message' => "Todo " . $args['id']
. " updated successfully"]);
}

return $response->withJson(['message' => 'Todo Not Found'], 404);


});

$this->delete('/{id}', function (Request $request, Response $response,


$args) use ($todoIdValid) {
if ($todoIdValid($args['id'])) {
return $response->withJson(['message' => "Todo " . $args['id']
. " deleted successfully"]);
}

return $response->withJson(['message' => 'Todo Not Found'], 404);


6 / 11
How to Use PHPUnit With Slim Framework 3.md 1/8/2023

});
});

$this->app = $app;
}

/**
* Get an instance of the application.
*
* @return \Slim\App
*/
public function get()
{
return $this->app;
}
}

Because of the new namespace, Pyjac, we just included in our app, we need to update our composer.json file
to inform composer on how to autoload files in the src directory.

{
"name": "pyjac/slim-todo",
"authors": [
{
"name": "Pyjac",
"email": "email@email.com"
}
],
"require": {
"slim/slim": "^3.0"
},
"require-dev": {
"phpunit/phpunit": "5.4.*"
},
"autoload": {
"psr-4": {
"Pyjac\\TodoAPI\\": "src"
}
}
}

To let composer be aware of our changes, we run

composer dump-autoload

Our slimmed down version of index.php is now

7 / 11
How to Use PHPUnit With Slim Framework 3.md 1/8/2023

<?php

require '../vendor/autoload.php';

// Run app
$app = (new Pyjac\TodoAPI\App())->get();
$app->run();

With this, we can now create an instance of our app and pass in test requests to the app to simulate API calls.

Let’s test GET request to the app. To do this, we make use of theSlim\Http\Environment class to mock HTTP
headers and build up the Request object from the headers using Slim\Http\Request.

<?php

use Pyjac\TodoAPI\App;
use Slim\Http\Environment;
use Slim\Http\Request;

class TodoTest extends PHPUnit_Framework_TestCase


{
protected $app;

public function setUp()


{
$this->app = (new App())->get();
}

public function testTodoGet()


{
$env = Environment::mock([
'REQUEST_METHOD' => 'GET',
'REQUEST_URI' => '/',
]);
$req = Request::createFromEnvironment($env);
$this->app->getContainer()['request'] = $req;
$response = $this->app->run(true);
$this->assertSame($response->getStatusCode(), 200);
$this->assertSame((string)$response->getBody(), "Hello, Todo");
}
}

Notice how we called $this->app->run(true). This tells Slim to run the app instance and instead of return
HTTP headers and echoing the body of the response it should return an object.

Run the test

8 / 11
How to Use PHPUnit With Slim Framework 3.md 1/8/2023

./vendor/bin/phpunit ./test/TodoTest.php

You should have a result similar to this

PHPUnit 5.4.8 by Sebastian Bergmann and contributors.


. 1 / 1 (100%)
Time: 51 ms, Memory: 4.50MB
OK (1 test, 2 assertions)

Congrats, we’re just written our first API test that hits an endpoint in our app and validate the correctness of
the status code and the response body.

Let’s test other endpoints.

Let’s test GET request to /todo

public function testTodoGetAll() {


$env = Environment::mock([
'REQUEST_METHOD' => 'GET',
'REQUEST_URI' => '/todo',
]);
$req = Request::createFromEnvironment($env);
$this->app->getContainer()['request'] = $req;
$response = $this->app->run(true);
$this->assertSame($response->getStatusCode(), 200);
$result = json_decode($response->getBody(), true);
$this->assertSame($result["message"], "Hello, Todo");
}

For POST request to /todo/{id}

Note: You can use the same pattern for PUT and PATCH

public function testTodoPost() {


$id = 1;
$env = Environment::mock([
'REQUEST_METHOD' => 'POST',
'REQUEST_URI' => '/todo/'.$id,
'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
]);
$req = Request::createFromEnvironment($env)->withParsedBody([]);
$this->app->getContainer()['request'] = $req;
$response = $this->app->run(true);
$this->assertSame($response->getStatusCode(), 200);
$result = json_decode($response->getBody(), true);
$this->assertSame($result["message"], "Todo ".$id." updated successfully");
}
9 / 11
How to Use PHPUnit With Slim Framework 3.md 1/8/2023

For DELETE request /todo/{id}

public function testTodoDelete() {


$id = 1;
$env = Environment::mock([
'REQUEST_METHOD' => 'DELETE',
'REQUEST_URI' => '/todo/'.$id,
'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
]);
$req = Request::createFromEnvironment($env)->withParsedBody([]);
$this->app->getContainer()['request'] = $req;
$response = $this->app->run(true);
$this->assertSame($response->getStatusCode(), 200);
$result = json_decode($response->getBody(), true);
$this->assertSame($result["message"], "Todo ".$id." deleted successfully");
}

Putting all together

<?php

use Pyjac\TodoAPI\App;
use Slim\Http\Environment;
use Slim\Http\Request;

class TodoTest extends PHPUnit_Framework_TestCase


{
protected $app;

public function setUp()


{
$this->app = (new App())->get();
}

public function testTodoGet()


{
$env = Environment::mock([
'REQUEST_METHOD' => 'GET',
'REQUEST_URI' => '/',
]);
$req = Request::createFromEnvironment($env);
$this->app->getContainer()['request'] = $req;
$response = $this->app->run(true);
$this->assertSame($response->getStatusCode(), 200);
$this->assertSame((string)$response->getBody(), "Hello, Todo");
}

10 / 11
How to Use PHPUnit With Slim Framework 3.md 1/8/2023

public function testTodoGetAll()


{
$env = Environment::mock([
'REQUEST_METHOD' => 'GET',
'REQUEST_URI' => '/todo',
]);
$req = Request::createFromEnvironment($env);
$this->app->getContainer()['request'] = $req;
$response = $this->app->run(true);
$this->assertSame($response->getStatusCode(), 200);
$result = json_decode($response->getBody(), true);
$this->assertSame($result["message"], "Hello, Todo");
}

public function testTodoPost()


{
$id = 1;
$env = Environment::mock([
'REQUEST_METHOD' => 'POST',
'REQUEST_URI' => '/todo/' . $id,
'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
]);
$req = Request::createFromEnvironment($env)->withParsedBody([]);
$this->app->getContainer()['request'] = $req;
$response = $this->app->run(true);
$this->assertSame($response->getStatusCode(), 200);
$result = json_decode($response->getBody(), true);
$this->assertSame($result["message"], "Todo " . $id . " updated
successfully");
}

public function testTodoDelete()


{
$id = 1;
$env = Environment::mock([
'REQUEST_METHOD' => 'DELETE',
'REQUEST_URI' => '/todo/' . $id,
'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
]);
$req = Request::createFromEnvironment($env)->withParsedBody([]);
$this->app->getContainer()['request'] = $req;
$response = $this->app->run(true);
$this->assertSame($response->getStatusCode(), 200);
$result = json_decode($response->getBody(), true);
$this->assertSame($result["message"], "Todo " . $id . " deleted
successfully");
}
}

Thank you for reading

Check out Oyebanji’s Github repo to understand how he implemented Slim in his project.

11 / 11

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy