VGTech is a blog where the developers and devops of Norways most visited website share code and tricks of the trade… Read more



Are you brilliant? We're hiring. Read more

Using PHP’s built-in web server in Behat tests

PHP

Behat is a tool for running acceptance tests for your application. If your application is a web application you will need a web server to execute your tests. This is not likely an issue when running your tests locally since you probably have a web server running on the development server that you can use, but when you execute your tests on Travis-CI for instance (you use Travis-CI right? RIGHT?!) it can be cumbersome getting a local Apache up and running for your test suite to use.

Some weeks back I wrote a post showing you how to use PHP’s built in web server in PHPUnit. This post will show you how to do the same for Behat when running your acceptance tests.

To fully understand everything that is mentioned in this post you need some knowledge of how Behat works, so if you have never used the tool before I would recommend skimming the Quick Intro to Behat at behat.org first.

I have also created a repository at GitHub with the code you need to be able to get started using PHP’s built in web server in your Behat test suite. This post will explain what the code does.

First, go ahead and clone the repository:

Show code
git clone https://github.com/vgnett/tech.vg.no-1252.git
cd tech.vg.no-1252

To install Behat you need to use Composer:

Show code
curl https://getcomposer.org/installer | php
php composer.phar install

Now you should have all you need to execute the Behat test suite. Try to execute the following command:

Show code
vendor/bin/behat -c .behat.yml

You should see something like this in your terminal:

Show code
No scenarios
No steps
0m0.006s

The repository you just cloned does not contain any actual code to test, nor any features for Behat to execute, so everything seems to be working as expected.

What has happened behind the scenes when running the above command is that Behat’s feature context spawned a web server as the suite started, and then proceeded to kill it when the suite finished. To achieve this I have some custom code in the feature context class that hooks into two events: @BeforeSuite and @AfterSuite. These are two of many other hooks you can use in your tests. Check out Hooking into the Test Process – Hooks for more info.

First, let’s take a look at the @BeforeSuite code:

Show code
/**
 * Start up the web server
 *
 * @BeforeSuite
 */
public static function setUp(SuiteEvent $event) {
    // Fetch config
    $params = $event->getContextParameters();
    $url = parse_url($params['url']);
    $port = !empty($url['port']) ? $url['port'] : 80;

    if (self::canConnectToHttpd($url['host'], $port)) {
        throw new RuntimeException('Something is already running on ' . $params['url'] . '. Aborting tests.');
    }

    // Try to start the web server
    self::$pid = self::startBuiltInHttpd(
        $url['host'],
        $port,
        $params['documentRoot']
    );

    if (!self::$pid) {
        throw new RuntimeException('Could not start the web server');
    }

    $start = microtime(true);
    $connected = false;

    // Try to connect until the time spent exceeds the timeout specified in the configuration
    while (microtime(true) - $start <= (int) $params['timeout']) {
        if (self::canConnectToHttpd($url['host'], $port)) {
            $connected = true;
            break;
        }
    }

    if (!$connected) {
        self::killProcess(self::$pid);
        throw new RuntimeException(
            sprintf(
                'Could not connect to the web server within the given timeframe (%d second(s))',
                $params['timeout']
            )
        );
    }
}

The first thing that happens is that we fetch the Behat configuration. The configuration is located in the .behat.yml file and uses the YAML format:

Show code
default:
  context:
    parameters:
      # URL to use against the web server in the features
      url: http://localhost:8888

      # Path to the document root
      documentRoot: public

      # How many seconds will we allow the httpd to use when starting?
      timeout: 1

The variables specified is used by Behat when starting the web server. url is the URL you want to use in your tests when accessing the web server. documentRoot is a path to the directory that should be used as a document root. The last parameter, timeout, is how long we want to allow the web server to use in seconds before we can connect to it. If you have a particularly slow machine you might need to set it to more than 1 second.

Now, back to the feature context code. After fetching the configuration we see if something is already running on the host and port combination. If this is the case we will simply throw an exception, halting the test suite.

Next up the script tries to start the web server, and then continues to try to connect to it for the amount of seconds specified in the configuration. If it is unable to connect we will throw another exception, again causing the test suite to abort. If we manage to connect to the web server the suite will continue as expected.

When the test suite have finished executing the tearDown method will be executed:

Show code
/**
 * Kill the httpd process if it has been started when the tests have finished
 *
 * @AfterSuite
 */
public static function tearDown(SuiteEvent $event) {
    if (self::$pid) {
        self::killProcess(self::$pid);
    }
}

Now it’s up to you to write feature specifications that actually uses the web server in your tests. I’ll leave that part for you. You’re welcome.

Senior developer at VG. Coder of code, drinker/brewer of beer and listener of metal/punk/hc. @cogocogo | @BeerNorway | www.beernorway.com


3 comments

Leave your comment