用 PHPUnit 测试 PHP 头

我正在尝试使用 PHPunit 来测试一个输出一些自定义头的类。

问题是在我的机器上:

<?php


class HeadersTest extends PHPUnit_Framework_TestCase {


public function testHeaders()
{
ob_start();


header('Location: foo');
$headers_list = headers_list();
header_remove();


ob_clean();


$this->assertContains('Location: foo', $headers_list);
}
}

甚至是这个:

<?php


class HeadersTest extends PHPUnit_Framework_TestCase {


public function testHeaders()
{
ob_start();


header('Location: foo');
header_remove();


ob_clean();
}
}

返回这个错误:

name@host [~/test]# phpunit --verbose HeadersTest.php
PHPUnit 3.6.10 by Sebastian Bergmann.


E


Time: 0 seconds, Memory: 2.25Mb


There was 1 error:


1) HeadersTest::testHeaders
Cannot modify header information - headers already sent by (output started at /usr/local/lib/php/PHPUnit/Util/Printer.php:173)


/test/HeadersTest.php:9


FAILURES!
Tests: 1, Assertions: 0, Errors: 1.

这看起来好像在测试运行之前有其他东西输出到终端,即使没有包含其他文件,并且在 PHP 标记开始之前没有其他字符。有没有可能是 PHP 单元内部的什么东西导致了这种情况?

有什么问题吗?

40658 次浏览

The issue is that PHPUnit will print a header to the screen and at that point you can't add more headers.

The work around is to run the test in an isolated process. Here is an example

<?php


class FooTest extends PHPUnit_Framework_TestCase
{
/**
* @runInSeparateProcess
*/
public function testBar()
{
header('Location : http://foo.com');
}
}

This will result in:

$ phpunit FooTest.php
PHPUnit 3.6.10 by Sebastian Bergmann.


.


Time: 1 second, Memory: 9.00Mb


OK (1 test, 0 assertions)

The key is the @runInSeparateProcess annotation.

If you are using PHPUnit ~4.1 or something and get the error:

PHP Fatal error:  Uncaught Error: Class 'PHPUnit_Util_Configuration' not found in -:378
Stack trace:
#0 {main}
thrown in - on line 378


Fatal error: Uncaught Error: Class 'PHPUnit_Util_Configuration' not found in - on line 378


Error: Class 'PHPUnit_Util_Configuration' not found in - on line 378


Call Stack:
0.0013     582512   1. {main}() -:0

Try add this to your bootstrap file to fix it:

<?php
if (!defined('PHPUNIT_COMPOSER_INSTALL')) {
define('PHPUNIT_COMPOSER_INSTALL', __DIR__ . '/path/to/composer/vendors/dir/autoload.php');
}

Although running the test in a separate process does fix the problem, there's a noticeable overhead when running a large suite of tests.

My fix was to direct phpunit's output to stderr, like so:

phpunit --stderr <options>

That should fix the problem, and it also means that you don't have to create a wrapper function and replace all occurrences in your code.

I had a more radical solution, in order to use $_SESSION inside my tested/included files. I edited one of the PHPUnit files at ../PHPUnit/Utils/Printer.php to have a "session_start();" before the command "print $buffer".

It worked for me like a charm. But I think "joonty" user's solution is the best of all up to now.

An alternative solution to @runInSeparateProcess is to specify the --process-isolation option when running PHPUnit:

name@host [~/test]# phpunit --process-isolation HeadersTest.php

That is analogous to set the processIsolation="true" option in phpunit.xml.

This solution has similar advantages/disadvantages to specifying the --stderr option, which however did not work in my case. Basically no code changes are necessary, even though there may be a performance hit due to running each test in a separate PHP process.

As an aside: For me headers_list() kept returning 0 elements. I noticed @titel's comment on the question and figured it deserves special mention here:

Just wanted to cover this if there are some other people interested in this as well. headers_list() doesn't work while running PHPunit (which uses PHP CLI) but xdebug_get_headers() works instead.

HTH

As already mentioned in a comment, I think it's a better solution to define processIsolation in the XML config file like

     <?xml version="1.0" encoding="UTF-8"?>
<phpunit
processIsolation            = "true"
// ...
>
</phpunit>

Like this, you don't have to pass the --stderr option, which might irritate your co-workers.

Use --stderr parameter for getting headers from PHPUnit after your tests.

phpunit --stderr

if you're using Laravel and you're adding some headers in route file

then you need to surround with headers_sent to ignore during tests

this is example:

if (!headers_sent()) {
header('Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Origin, Access-Control-Allow-Origin,  Content-Type, X-Authorization, Authorization, Accept,charset,boundary,Content-Length');
header('Access-Control-Allow-Origin: *');
}

then try unit test again it will pass..