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:
1 2 | [code language="shell"]git clone https://github.com/vgnett/tech.vg.no-1252.git cd tech.vg.no-1252[/code] |
To install Behat you need to use Composer:
1 2 | [code language="shell"]curl https://getcomposer.org/installer | php php composer.phar install[/code] |
Now you should have all you need to execute the Behat test suite. Try to execute the following command:
1 | [code language="shell"]vendor/bin/behat -c .behat.yml[/code] |
You should see something like this in your terminal:
1 2 3 | [code language="text"]No scenarios No steps 0m0.006s[/code] |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | [code language="php"]/** * 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'] ) ); } }[/code] |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 | [code language="text"] 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 [/code] |
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:
1 2 3 4 5 6 7 8 9 10 | [code language="php"]/** * 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); } }[/code] |
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.