109

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?

SamHennessy
  • 4,288
  • 2
  • 18
  • 17
titel
  • 3,454
  • 9
  • 45
  • 54
  • 18
    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. – titel Mar 18 '12 at 17:26

8 Answers8

134

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');
}
b01
  • 4,076
  • 2
  • 30
  • 30
SamHennessy
  • 4,288
  • 2
  • 18
  • 17
  • 7
    this causes errors in there is some define() statements PHPUnit_Framework_Exception: Notice: Constant xyz already defined – Minhaz Jul 02 '14 at 21:04
  • 1
    @mebjas That sounds unrelated. – SamHennessy Jul 02 '14 at 21:40
  • I meant if I have to test headers in phpunit, and I have `define` statements in my code, this method causes errors, as I mentioned – Minhaz Jul 04 '14 at 21:28
  • @mebjas adding a if with a defined() is maybe what you need to add around your define. If you need to change the value of your constants to test your code then you will have issues. – SamHennessy Jul 09 '14 at 19:35
  • 4
    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` – t1gor Jan 05 '15 at 17:57
  • @t1gor try adding to the command --no-globals-backup . See https://phpunit.de/manual/current/en/fixtures.html#fixtures.global-state – SamHennessy Jan 05 '15 at 22:23
  • @SamHennessy, globals backup is disabled for all my tests, but thanks. Any other ideas? – t1gor Jan 06 '15 at 09:24
  • 2
    This is definitely the best option to solve the problem. It worked like a charm! – xarlymg89 Jun 12 '15 at 09:47
  • 2
    I had to use xdebug_get_headers(), to get the array of set headers. headers_list() global function did not work in my case. – Shalom Sam Nov 08 '15 at 13:21
  • I got the issue with defines and "constant already defined". It turned out that I had define() in my phpunit config.php. Wrapping them in an if-defined solved it. Seems like the config-file is loaded twice, which seems reasonable when thinking about it. – Mikael Roos Dec 07 '15 at 16:55
  • This caused the tests to hang indefinitely. – fraxture Feb 29 '16 at 16:31
  • @fraxture which part exactly? Are you talking about the answer or one of the comments. – SamHennessy Feb 29 '16 at 22:23
  • for an old thread. i tried this solution but unfortunately the test did not run completely even though it did echo `OK (1 test, 0 assertions)`. I noticed this by tracing the code. I was having a problem with `session_start()` `Cannot send session cookie - headers already sent` I guess what it did was it stop the test right after the error. and continued on. because the test did not pass through my tracing. – xCHAN Oct 27 '17 at 02:05
111

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.

Jon Cairns
  • 11,783
  • 4
  • 39
  • 66
  • 3
    Brilliant! No changes to my code, no separate processes, and it works. – alexfernandez Oct 28 '12 at 18:19
  • but will I still see errors I made and get output of error_reporting(E_ALL) ?? – spankmaster79 Apr 02 '13 at 09:27
  • 1
    @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. – Jon Cairns Apr 02 '13 at 10:22
  • i was becoming made!!! tnx for this trick, it does make sense, errors should be reported to the stderror!!! Tnx – th3n3rd Sep 20 '13 at 13:27
  • 45
    You can add `stderr="true"` in your phpunit.xml to save a few keystrokes. – tszming Aug 27 '14 at 05:29
  • I know this is an old thread, but the problem i have on this solution is that it does not generate a `junit.xml` file from `phpunit.xml` for my unit test. I guess it is because it outputs to stderr instead of stdout. – xCHAN Oct 26 '17 at 03:19
17

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

Community
  • 1
  • 1
Melle
  • 7,639
  • 1
  • 30
  • 31
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.

PepeNietnagel
  • 230
  • 2
  • 10
  • 4
    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 Sep 03 '17 at 20:48
  • @Shi – How do you do that? The `processIsolation` attribute isn't allowed on the `` or `` element. – Richard Smith Feb 25 '23 at 11:27
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.

Majid Golshadi
  • 2,686
  • 2
  • 20
  • 29
Sergio Abreu
  • 2,686
  • 25
  • 20
1

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

phpunit --stderr
NSukonny
  • 1,090
  • 11
  • 18
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..

Alaa Moneam
  • 509
  • 5
  • 10
  • 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. – Dale Ryan Jul 18 '22 at 08:29
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.

AlexB
  • 53
  • 1
  • 5
  • 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. – Shi Sep 03 '17 at 20:51
  • who ever downgraded #fail. this answer is correct as another option. – f b Jun 19 '18 at 18:47