Test PHP headers with PHPUnit
Asked Answered
P

8

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?

Pneumoencephalogram answered 16/3, 2012 at 22:4 Comment(1)
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.Pneumoencephalogram
M
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');
}
Midge answered 17/3, 2012 at 0:36 Comment(13)
this causes errors in there is some define() statements PHPUnit_Framework_Exception: Notice: Constant xyz already definedRoller
@mebjas That sounds unrelated.Midge
I meant if I have to test headers in phpunit, and I have define statements in my code, this method causes errors, as I mentionedRoller
@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.Midge
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:211Bevvy
@Bevvy try adding to the command --no-globals-backup . See phpunit.de/manual/current/en/…Midge
@SamHennessy, globals backup is disabled for all my tests, but thanks. Any other ideas?Bevvy
This is definitely the best option to solve the problem. It worked like a charm!Councilor
I had to use xdebug_get_headers(), to get the array of set headers. headers_list() global function did not work in my case.Patentee
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.Mucosa
This caused the tests to hang indefinitely.Bobbysoxer
@Bobbysoxer which part exactly? Are you talking about the answer or one of the comments.Midge
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.Verein
A
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.

Austriahungary answered 30/5, 2012 at 12:3 Comment(6)
Brilliant! No changes to my code, no separate processes, and it works.Heida
but will I still see errors I made and get output of error_reporting(E_ALL) ??Carcinogen
@Carcinogen 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.Austriahungary
i was becoming made!!! tnx for this trick, it does make sense, errors should be reported to the stderror!!! TnxSherborn
You can add stderr="true" in your phpunit.xml to save a few keystrokes.Bern
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.Verein
F
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

Fax answered 6/10, 2016 at 9:29 Comment(1)
Error : Call to undefined function xdebug_get_headers() in PHPUnit 9.5.2Snack
M
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.

Methenamine answered 12/6, 2017 at 20:30 Comment(2)
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.Glidewell
@Glidewell – How do you do that? The processIsolation attribute isn't allowed on the <testsuite> or <file> element.Rufe
S
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.

Swedish answered 9/11, 2013 at 13:12 Comment(2)
Did you submit the pull-request in the repository? I guess this might be a common problem.Bevvy
Thanks for the hint, I called session_start() in my phpunit bootstrap.php and it works for mePruter
M
1

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

phpunit --stderr
Melee answered 18/2, 2020 at 8:0 Comment(0)
E
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..

Elnaelnar answered 23/5, 2021 at 9:17 Comment(1)
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.Ressieressler
T
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.

Texas answered 23/1, 2016 at 17:2 Comment(2)
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.Glidewell
who ever downgraded #fail. this answer is correct as another option.Navada

© 2022 - 2024 — McMap. All rights reserved.