Tag Archives: unit test

Today I wrote a class called “HttpLoader” that downloads an external web page. Later on it will be used for pingback handling. My problem was though that it’s not easy to test such a class. I could have downloaded http://example.org or something like this in my unit test, but I didn’t think that this would be the best solution.
After a while I thought: “Well, there’s a built-in webserver in PHP 5.4! So why don’t use it for my tests?”. Additionaly I knew about the Process component of Symfony and ready was my abstract test class:

use PHPUnit_Framework_TestCase;
use Symfony\Component\Process\Process;
 
/**
 * Abstract TestCase that allows to start the built-in PHP server
 * with a custom router script.
 */
abstract class AbstractHttpTest extends PHPUnit_Framework_TestCase
{
    /**
     * The default port the server should listen on.
     */
    const TEST_PORT = 8181;
 
    /**
     * The server process.
     *
     * @var Process
     */
    private $process = null;
 
    /**
     * The port of the current server process.
     *
     * @var int
     */
     private $port;
 
    /**
     * Sets up a test and checks if the requirements for the server are met.
     */
    public function setUp()
    {
        if (!version_compare(PHP_VERSION, '5.4.0', '>='))
        {
            $this->markTestSkipped("PHP 5.4 is neccessary to use the test server");
        }
 
        if (!class_exists('Symfony\\Component\\Process\\Process')) {
            $this->markTestSkipped("Symfony Process library is not available");
        }
    }
 
    /**
     * Starts the test server.
     *
     * @param string $router Router script.
     * @param int $port Server port.
     */
    public function startServer($router, $port = 0)
    {
        if ($this->process) {
            $this->process->stop();
        }
 
        $this->port = $port ? $port : static::TEST_PORT;
        $command = sprintf(PHP_BINARY . ' -S 127.0.0.1:%d %s', $this->port, $router);
 
        $this->process = new Process($command);
        $this->process->start();
 
        // Give the server some time to start
        $now = time();
        while ($this->process->isRunning() && time() - $now < 2) {
            $handle = @fsockopen('127.0.0.1', $this->port);
            if ($handle !== false) {
                fclose($handle);
                return;
            }
        }
 
        $this->fail('Could not start webserver');
    }
 
    /**
     * Stops the server.
     */
    public function stopServer()
    {
        if ($this->process) {
            $this->process->stop();
        }
    }
 
    /**
     * Kills the server gracefully.
     */
    public function __destruct()
    {
        $this->stopServer();
    }
 
    public function createUrl($path)
    {
        return sprintf('http://127.0.0.1:%d%s', $this->port, $path);
    }
}

Now it’s pretty easy to test HTTP interactions:

class HttpLoaderTest extends AbstractHttpTest
{
    public function testUrlFailure()
    {
        $loader = new HttpLoader();
        $this->assertFalse($loader->load('this://does/not:work!'));
    }
 
    public function testSuccessfulRequestWithoutData()
    {
        $this->startServer(__DIR__ . '/../Fixtures/success_router.php');
 
        $loader = new HttpLoader();
        $response = $loader->load($this->createUrl('/foo/bar'));
 
        $this->assertEquals(200, $response['status']);
        $this->assertEquals('', $response['content']);
        $this->assertEquals('/foo/bar', $response['headers']['Foo-Bar']);
 
        $this->stopServer();
    }
 
    public function testSuccessfulRequestWithData()
    {
        $this->startServer(__DIR__ . '/../Fixtures/success_router.php');
 
        $loader = new HttpLoader();
        $response = $loader->load($this->createUrl('/foo/bar'), 'foo bar');
 
        $this->assertEquals('foo bar', $response['content']);
 
        $this->stopServer();
    }
 
    public function test404Error()
    {
        $this->startServer(__DIR__ . '/../Fixtures/error_router.php');
 
        $loader = new HttpLoader();
        $response = $loader->load($this->createUrl('/'));
 
        $this->assertEquals(404, $response['status']);
 
        $this->stopServer();
    }
}

And that’s it for today :)

Tags: , , , , , ,