Unit Testing with items that need to send headers

Viewed 21175

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 Answers

Well, your session manager is basically broken by design. To be able to test something, it must be possible to isolate it from side effects. Unfortunately, PHP is designed in such a way, that it encourages liberal use of global state (echo, header, exit, session_start etc. etc.).

The best thing you can do, is to isolate the side-effects in a component, that can be swapped at runtime. That way, your tests can use mocked objects, while the live code uses adapters, that have real side-effects. You'll find that this doesn't play well with singletons, which I presume you're using. So you'll have to use some other mechanism for getting shared objects distributed to your code. You can start with a static registry, but there are even better solutions if you don't mind a bit of learning.

If you can't do that, you always have the option of writing integration-tests. Eg. use the PHPUnit's equivalent of WebTestCase.

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();
   }
}

Can't you use output buffering before starting the test? If you buffer everything that is output, you shouldn't have problems setting any headers as no output would have yet been sent to client at that point.

Even if OB is used somewhere inside your classes, it is stackable and OB shouldn't affect what's going on inside.

As far as I know Zend Framework uses the same output buffering for their Zend_Session package tests. You can take a look at their test cases to get you started.

Related