12

I have a unit test that fails because headers are already sent. However, the header in this scenario is expected.

How do I tell PHPUnit to expect a 500 header?

I've read this question but it didn't help.

The method is wrapped inside an output buffer.

ob_start();
$foo->methodWhichSendsHeader();
ob_clean();
kenorb
  • 155,785
  • 88
  • 678
  • 743
bcmcfc
  • 25,966
  • 29
  • 109
  • 181

3 Answers3

21

If you have xdebug installed you can use xdebug_get_headers() to get the headers. Then you can test them as needed.

$headers=xdebug_get_headers();

gets you an array which looks like...

array(
    0 => "Content-type: text/html",
    1 => ...
)

So you'll need to parse each header line to separate the header name from the value

Peter Bagnall
  • 1,794
  • 18
  • 22
  • This works great for calls of the format `header('Content-Type: text/html');` but it doesn't seem to work for calls of the format `header('HTTP/1.1 500 Internal Server Error');` is there anyway to get these headers / status codes? – Derokorian Oct 12 '14 at 16:31
  • While you can't get the actual header contents from `header('HTTP/1.1 500 Internal Server Error');`, you *can* use the built-in [http_response_code()](http://php.net/manual/en/function.http-response-code.php) to retrieve the HTTP status code that will be returned to the browser. – JSmitty Aug 29 '16 at 14:48
3

If you can't use xdebug_get_headers on your system, another approach is to mock the header function.

I'm using the following now, which works great. Lets say you have this code...

<?php
header('Content-type: text/plain; charset=UTF-8');
...

I replace header with a header function which is testable like this...

<?php
Testable::header('Content-type: text/plain; charset=UTF-8');
...

The Testable class is implemented as follows. Note that functions just need to be prepended with Testable::. Otherwise they work just the same as the usual functions.

class Testable {
   private static $headers=array();

   static function header($header) {
      if (defined('UNIT_TESTING')) {
         self::$headers[]=$header;
      } else {
         header($header);
      }
   }

   public static function reset() {
      self::$headers=array();
   }

   public static function headers_list() {
      if (defined('UNIT_TESTING')) {
          return self::$headers;
      } else {
          return headers_list();
      }
   }
}

Now all you need to do is define UNIT_TESTING in your tests, but not in production. Then when you come to test your headers, just call Testable::headers_list().

You should of course add methods for setcookie, headers_sent and any other functions which issue HTTP headers.

Peter Bagnall
  • 1,794
  • 18
  • 22
  • @JimmyKane - No more than any mock, as far as I can tell. You can leave the call to Testable::header in production code, so long as in production UNIT_TESTING is not defined. Is there something I'm missing here? – Peter Bagnall Dec 18 '13 at 19:10
  • 1
    What I meant is that you have to add a static function to your class and thus modify the `Testable` class. I don't want to edit and add a static this in every abstract that sends headers in order to be testable. But that's just a preference. Other than that your answer is good. Have a great day. – Jimmy Kane Dec 18 '13 at 19:33
  • Ah, the Testable class isn't the class under test. It's called that to mean "here's a header function which is testable" as opposed to the standard one. That's not very clear with the class name I chose, I'll edit to make it clearer. – Peter Bagnall Dec 18 '13 at 22:36
  • 1
    Hopefully that's now a bit clearer. Thanks for your comments, definitely made for a better answer! – Peter Bagnall Dec 18 '13 at 22:55
1

Another possible approach is to override the header php function for the namespace you are testing. https://www.codepunker.com/blog/overwrite-built-in-php-functions-using-namespaces

namespace My\Application\Namespace;
use My\Test\Application\Namespace;    

//this overrides the header function for that namespace
//it works only if the function is called without the backslash
function header($string){
    HeaderCollector::$headers[] = $string;
}

namespace My\Test\Application\Namespace

/**
 * Class HeaderCollector
 * Using this in combination with function header override
 * for the namespace My\Application\Namespace
 * we can make assertions on headers sent
 */
class HeaderCollector {

    public static $headers = [];

    //call this in your test class setUp so headers array is clean before each test
    public static function clean() {
        self::$headers = [];
    }
}

Then in your test class

namespace My\Test\Application\Namespace
use PHPUnit\Framework\TestCase;


class MyTest extends TestCase {

    protected function setUp() {
        parent::setUp();
        //clean for each test
        HeaderCollector::clean();
    }

    public function testHeaders() {
        //call the code that send headers
        ...

        self::assertEquals(
            ["Content-Type: text/html; charset=UTF-8", "Another-Header..."],
            HeaderCollector::$headers
        );
    }
}

You can keep your code clean and you don't need xdebug

Francesco
  • 443
  • 1
  • 8
  • 16