6

I'm trying to make some unit tests using a setcookie() function in a pretty good IDE PhpStorm. But I get a following error every time:

Cannot modify header information - headers already sent by (output started at /tmp/phpunit.php:418)

Probably the reason of this error is print('some text') with flush() before setcookie() calling. But flushing is performed in a /tmp/phpunit.php file generated by PhpStorm. While setcookie() is called from my sources. So I can't edit the generated file to do some kind of output buffering. Also there is some another moment: PhpStorm executes /tmp/phpunit.php script like this:

/usr/bin/php /tmp/phpunit.php -config /var/www/.../protected/tests/phpunit.xml d /var/www/.../protected/tests/unit/user

Please help me to workaround this issue. How can I run unit tests from PhpStorm directly?

alexey_detr
  • 1,640
  • 1
  • 17
  • 15
  • possible duplicate of [PHPUnit - Unit Testing with items that need to send headers](http://stackoverflow.com/questions/190292/phpunit-unit-testing-with-items-that-need-to-send-headers) – outis Sep 24 '12 at 20:26
  • I was using the following: phpunit --stderr That worked when using setcookie. http://stackoverflow.com/questions/9745080/test-php-headers-with-phpunit – HMR Nov 26 '12 at 05:04

4 Answers4

6

I found a simpler solution. Consider this class:

class Cookie
{
    public function set($name, $value)
    {
        return setcookie($name, $value);
    }
}

The test will throw the error described in the question unless you set the annotation @runInSeparateProcess

class CookieTest
{
    /**
     * @runInSeparateProcess
     */
    function test_set()
    {
        $cookie = new Cookie;
        $this->assertTrue($cookie->set('mycookie', 'myvalue');
    }
}
John Linhart
  • 1,746
  • 19
  • 23
  • 1
    The test will fail because `Cookie::set` returns void. – amphetamachine Jul 25 '17 at 22:26
  • @JohnLinhart This is just suppressing the error, but the value is not actually set in the (separate process). So you can't test code and assert $_COOKIE['mycookie'] == 'myvalue'. You'll get an undefined index error. – Smamatti Oct 26 '19 at 19:44
6

One possible way around this is to use a 'mock' replacement for the setcookie() function.

This is a common technique in unit testing, where you want to test something that relies on an external class or function that you don't want to affect the current test.

The way to do it would be to create a stub function definition for setcookie() within you unit test code. This would then be called during the test instead of the real setcookie() function. Exactly how you implement this stub function is up to you, and would depend on what your code uses it for.

The major problem you'll have with this approach is that PHP doesn't allow you to override existing functions by default - if you try this on a standard PHP installation, you'll get "error: function cannot be redeclared".

The solution to this problem is PHP's Runkit extension, which is explicitly designed for this kind of testing, and allows you to rename an existing function, including built-in ones.

If you configure the PHP installation in your testing environment to include the Runkit extension, you will be able to do this kind of test.

Hope that helps.

Spudley
  • 166,037
  • 39
  • 233
  • 307
  • Thanks a lot for your answer, Spudley! It's a really good idea. I'm going to try Runkit on my testing environment soon. – alexey_detr Mar 29 '11 at 10:31
  • 7
    If you don't want to or cannot use RunKit (even if you can), I recommend creating a class that encapsulates all the cookie manipulation. You can then create a mock object for that class to avoid calling `setcookie()` entirely. – David Harkness Mar 29 '11 at 16:00
  • 1
    Thanks for your answer, David! It seems that your point is even more applicable. But I've already succeed with Runkit by redefining `setcookie()` like this: `runkit_function_redefine('setcookie', '$name, $value = null, $expire = null, $path = null, $domain = null, $secure = null, $httponly = null', '');` – alexey_detr Mar 30 '11 at 04:12
  • The links are 404. – Steve Moretz Dec 12 '21 at 10:09
0

One another workaround without creating proxy class and mocking it, but requires change on working code level.

function setTestableCookie(string $key, $value): void
{
    $_COOKIE[$key] = $value;

    if (headers_sent() === false) {
        setcookie(
          $key,
          $value,
          ... other cookie params ...
        );
    }
}

This way it will work in unit tests and set proper cookie params on production code.

Konrad Gałęzowski
  • 1,761
  • 1
  • 20
  • 29
-1

Instead of trying to set actual cookie headers (which will fail because content has already been sent); For test purposes, you may simply set the COOKIE superglobal explicitly:

$_COOKIE['mycookie']=myvalue';
matwr
  • 1,548
  • 1
  • 13
  • 23
  • Downvoted this since it was not a working solution, but can't undo that now. Actually this works for me now in combination with the `@runInSeparateProcess` annotation. Otherwise the 'header already sent' error still popped up. – Smamatti Oct 27 '19 at 09:28