Skip to content

Latest commit

 

History

History
88 lines (70 loc) · 2.87 KB

2023-03-13-getting-laminas-http-handler-runner-working-with-phpunit.md

File metadata and controls

88 lines (70 loc) · 2.87 KB
title tags growthStage
Getting Laminas HttpHandlerRunner to play nice with PHPUnit
PHP
budding

Preface

At the beginning of this month I de-mothballed both my [[Tuppence|PHP microfamework: Tuppence]] project and the related [[Tuppence Boilerplate|Tuppence Boilerplate Project]]. In doing so started encountering the Laminas\HttpHandlerRunner\Emitter\SapiEmitter class throwing a EmitterException when invoked within PHPUnit:

Laminas\HttpHandlerRunner\Exception\EmitterException : Unable to emit response; headers already sent in /vendor/phpunit/phpunit/src/Util/Printer.php:138

This doesn't happen when the route being tested is run from a browser which makes me suspect that this is picking up PHPUnit's command line output.

Cause

Looking into the stack trace I could see that the EmitterException was being thrown by SapiEmitterTrait::assertNoPreviousOutput() and quick look inside that function we can see it uses headers_sent to check if headers have already been sent, throwing the exception if so.

var_dump(headers_sent());
echo "Hello world".PHP_EOL;
var_dump(headers_sent());

// Outputs:
// bool(false)
// Hello World
// bool(true)

PHPUnit always outputs its version before running your tests, and it's this that is causing headers_sent to return true.

Solution

The [[Minimalism|minimalist]] solution is to replace the SapiEmitter being used in your tests for a TestEmitter that implements the same EmitterInterface but doesn't do all the checks that break in a test environment. A benefit of using your own TestEmitter is that you can obtain the response and run assertions in your tests.

<?php  
  
namespace App\Tests;  
  
use Psr\Http\Message\ResponseInterface;  
use Laminas\HttpHandlerRunner\Emitter\EmitterInterface;  
  
class TestEmitter implements EmitterInterface  
{  
    private ResponseInterface $response;  
  
    public function emit(ResponseInterface $response): bool  
    {  
        $this->response = $response;  
        return true;  
    }  
    public function getResponse(): ResponseInterface  
    {  
        return $this->response;  
    }}
}

I use this within the Tuppence Boilerplate inside its BootsApp extension of TestCase this then lets me provide a couple of helpful assertions akin to those seen in Laravel:

class BootsApp extends TestCase 
{
	// ...

	protected function runRequest(ServerRequest $request): string
	{
	    $this->app->run($request);
	    return (string)$this->emitter->getResponse()->getBody();
	}

	protected function assertResponseOk(): void
	{
	    $this->assertEquals(
		    200,
		    $this->emitter->getResponse()->getStatusCode()
	    );
	}
 
	protected function assertResponseCodeEquals($code = 200): void
	{
	    $this->assertEquals(
		    $code,
		    $this->emitter->getResponse()->getStatusCode()
	    );
	}
}