111

I'm trying to use PHPunit to test a class that outputs some custom headers.

The problem is that on my machine this:

<?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);
    }
}

or even this:

<?php

class HeadersTest extends PHPUnit_Framework_TestCase {

    public function testHeaders()
    {
        ob_start();

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

        ob_clean();
    }
}

return this error:

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.

This looks as if there is something else outputting to the terminal before the test runs even though there is no other file included and there is no other character before the beginning of the PHP tag. Could it be something inside PHPunit that is causing this?

What could the issue be?

1
  • 19
    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. Commented Mar 18, 2012 at 17:26

8 Answers 8

136

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');
}
Sign up to request clarification or add additional context in comments.

13 Comments

this causes errors in there is some define() statements PHPUnit_Framework_Exception: Notice: Constant xyz already defined
@mebjas That sounds unrelated.
I got this when applied: PHP Fatal error: Uncaught exception 'Exception' with message 'Serialization of 'SplFileInfo' is not allowed' in phar:///usr/local/bin/phpunit/phpunit/Util/GlobalState.php:211
This is definitely the best option to solve the problem. It worked like a charm!
I had to use xdebug_get_headers(), to get the array of set headers. headers_list() global function did not work in my case.
|
112

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.

6 Comments

Brilliant! No changes to my code, no separate processes, and it works.
but will I still see errors I made and get output of error_reporting(E_ALL) ??
@spankmaster79 yes, it will just go to your terminal's standard error. By default, most terminals will print standard out and standard error together, but they are actually separate streams.
i was becoming made!!! tnx for this trick, it does make sense, errors should be reported to the stderror!!! Tnx
You can add stderr="true" in your phpunit.xml to save a few keystrokes.
|
20

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

1 Comment

Error : Call to undefined function xdebug_get_headers() in PHPUnit 9.5.2
7

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.

2 Comments

It probably is better to define it just for the test that requires it. Setting it for all tests will just make running the tests slow.
@Shi – How do you do that? The processIsolation attribute isn't allowed on the <testsuite> or <file> element.
3

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.

2 Comments

Did you submit the pull-request in the repository? I guess this might be a common problem.
Thanks for the hint, I called session_start() in my phpunit bootstrap.php and it works for me
1

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

phpunit --stderr

Comments

1

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..

1 Comment

I was working on a laravel 5.4 application and noticed the same header calls in ./bootstrap/app.php. Tried that solution of yours and it works! No --stderr or @runInSeparateProcess flags needed.
0

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.

2 Comments

This might be a first step in tracking down the cause and doing some tests, but in order to create maintainable tests, just directly embed the annotation into the test file. The fewer 'special' options are required from command line, the easier the maintenance of a CI system configuration is.
who ever downgraded #fail. this answer is correct as another option.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.