Unit Testing with items that need to send headers

2020-01-28 05:19发布

I'm currently working with PHPUnit to try and develop tests alongside what I'm writing, however, I'm currently working on writing the Session Manager, and am having issues doing so...

The constructor for the Session handling class is

private function __construct()
{
    if (!headers_sent())
    {
        session_start();
        self::$session_id = session_id();
    }
}

However, as PHPUnit sends out text before it starts the testing, any testing on this Object returns a failed test, as the HTTP "Headers" have been sent...

11条回答
一纸荒年 Trace。
2楼-- · 2020-01-28 05:22

As I'm unittesting my bootstrap right now (yes I know most of you don't do that), I'm running in to the same problem (both header() and session_start()). The solution I found is rather simple, in your unittest bootstrap define a constant and simply check it before sending the header or starting the session:

// phpunit_bootstrap.php
define('UNITTEST_RUNNING', true);

// bootstrap.php (application bootstrap)
defined('UNITTEST_RUNNING') || define('UNITTEST_RUNNING', false);
.....
if(UNITTEST_RUNNING===false){
    session_start();
}

I agree that this is not perfect by design, but I'm unittesting an existing application, rewriting large parts is not desired. I also using the same logic to test private methods using the __call() and __set() magic methods.

public function __set($name, $value){
    if(UNITTEST_RUNNING===true){
       $name='_' . $name;
       $this->$name=$value;
    }
    throw new Exception('__set() can only be used when unittesting!');
 }
查看更多
家丑人穷心不美
3楼-- · 2020-01-28 05:24

I had the same issue and I solved it by calling phpunit with --stderr flag just like this:

phpunit --stderr /path/to/your/test

Hope it helps someone!

查看更多
你好瞎i
4楼-- · 2020-01-28 05:31

The creation of the bootstrap file, pointed out 4 posts back seems the cleanest way around this.

Often with PHP we are having to maintain, and try to add some kind of engineering discipline to legacy projects that are abysmally put together. We don't have the time (or the authority) to ditch the whole pile of rubbish and start again, so the first anwer by troelskn isn't always possible as a way forward. ( If we could go back to the initial design, then we could ditch PHP altogether and use something more modern, such as ruby or python, rather than help perpetuate this COBOL of the web development world. )

If you are trying to write unit tests for modules that use session_start or setcookie throughout them, than starting the session in a boostrap file gets your round these issues.

查看更多
霸刀☆藐视天下
5楼-- · 2020-01-28 05:32

I think the "right" solution is to create a very simple class (so simple it doesn't need to be tested) that's a wrapper for PHP's session-related functions, and use it instead of calling session_start(), etc. directly.

In the test pass mock object instead of a real stateful, untestable session class.

private function __construct(SessionWrapper $wrapper)
{
   if (!$wrapper->headers_sent())
   {
      $wrapper->session_start();
      $this->session_id = $wrapper->session_id();
   }
}
查看更多
女痞
6楼-- · 2020-01-28 05:36

I'm wondering why nobody have listed XDebug option:

/**
 * @runInSeparateProcess
 * @requires extension xdebug
 */
public function testGivenHeaderIsIncludedIntoResponse()
{
    $customHeaderName = 'foo';
    $customHeaderValue = 'bar';

    // Here execute the code which is supposed to set headers
    // ...

    $expectedHeader = $customHeaderName . ': ' . $customHeaderValue;
    $headers = xdebug_get_headers();

    $this->assertContains($expectedHeader, $headers);
}
查看更多
对你真心纯属浪费
7楼-- · 2020-01-28 05:37

It seems that you need to inject the session so that you can test your code. The best option I have used is Aura.Auth for the authentication process and using NullSession and NullSegment for testing.

Aura testing with null sessions

The Aura framework is beautifully written and you can use Aura.Auth on its own without any other Aura framework dependencies.

查看更多
登录 后发表回答