Testing PHP PSR-7 Middleware

Lots of Middleware is being written for PHP, most of it compatible with PSR-7 messages - here's how I've been testing some middleware I've written for apps in Slim.

I had a CspMiddleware that took a string and a nonce and added it to the response headers, I just wanted a really simple test to ensure it was doing just that.

The test I came up with was the following:

<?php

namespace Tests\Http\Middleware;

use Slim\Http\Headers;
use Slim\Http\Request;
use Slim\Http\Response;
use Slim\Http\Stream;
use Slim\Http\Uri;
use PHPUnit\Framework\TestCase;
use App\Http\Middleware\CspMiddleware;

class CspMiddlewareTest extends TestCase
{

    /**
     * @test
     */
    public function it_adds_the_header_with_a_nonce_to_the_response()
    {
        $request = new Request(
            'GET',
            new Uri('', ''),
            new Headers([]),
            [],
            [],
            new Stream(stream_context_create([]))
        );
        $response = new Response;

        $middleware = new CspMiddleware(
            "default-src: 'none'; script-src: 'self' 'nonce-%s';",
            'nonce_string_here'
        );

        /**
         * @var ResponseInterface $middlewareResponse
         */
        $middlewareResponse = $middleware(
            $request,
            $response,
            function ($request, $response) {
                return $response;
            }
        );

        self::assertEquals(
            "default-src: 'none'; script-src: 'self' 'nonce-nonce_string_here';",
            $middlewareResponse->getHeader('Content-Security-Policy')[0]
        );
    }
}

Let's break it down

Arrange

We need to setup request and response objects to use later on. We could probably use a factory to create the request object as it's a bit fiddly, or even use something like https://github.com/zendframework/zend-diactoros (which is compatible with PSR-7) - but for this, I just new'd up an object with the various parameters and dependencies:

$request = new Request(
    'GET',
    new Uri('', ''),
    new Headers([]),
    [],
    [],
    new Stream(stream_context_create([]))
);
$response = new Response;

Next, we create our middleware that we're going to test with a known content security policy and nonce. The creation of these would be delegated to other classes in our application, as the nonce should be random per-request, but for the sake of this test, it doesn't need to be:

$middleware = new CspMiddleware(
    "default-src: 'none'; script-src: 'self' 'nonce-%s';",
    'nonce_string_here'
);

Act

Now we'll actually call our middleware, we pass a closure as the 3rd parameter purely for testing purposes, it just returns the response with no modifications so we can make assertions on it:

<?php

/**
 * @var ResponseInterface $middlewareResponse
 */
$middlewareResponse = $middleware(
    $request,
    $response,
    function ($request, $response) { 
        return $response;
    }
);

Assert

We need to assert that the string in the Content-Security-Policy header (note, getHeader returns an array, so we only want the first one) matches what we'd expect the policy to be once it's had the nonce added to it. It also ensures we've added it under the correct header:

self::assertEquals(
    "default-src: 'none'; script-src: 'self' 'nonce-nonce_string_here';",
    $middlewareResponse->getHeader('Content-Security-Policy')[0]
);