Mocking calls to shell commands in Behat tests.
Context
The context of the post is my ongoing effort to develop a wp-cli package to allow users to interactively scaffold tests for WordPress plugins and themes based on Codeception and wp-browser.
The scaffolding consists in creating some files like a Composer configuration file and then launching commands like composer update
or wpcept boostrap --interactive
later.
Using what the wp-cli cookbook and package scaffolding command provide I’m using Behat to develop the CLI commands with a behaviour-driven development approach.
Do we want to update Composer dependencies?
I’ve written about the interactivity tools wp-cli offers in a previous post and found myself in need of testing the step where the Composer composer.json
configuration file has been updated or created and the user is asked if the process should go on and run composer update
to download the dependencies from the web.
That’s a costly operation in terms of time and bandwidth that the user might want to postpone.
Should this be the case I’ve added a scenario to make sure the user is left with some instructions about the things to do:
Scenario: the command will end if the user wants to manually update composer dependencies
Given I will answer 'n' to the 'composer update' question
Given I run `composer init --name=lucatume/some --description=Some --author="Luca Tumedei <luca@theaveragedev.com>" -n` in the 'some-plugin' plugin
When I run `wp scaffold plugin some-plugin --plugin_name="Some Plugin" --plugin_description="Description of the plugin." --plugin_author="Your Name" --plugin_author_uri="http://example.com"`
And I run `wp wpb-scaffold plugin-tests some-plugin` with input
Then STDOUT should contain:
"""
All done
"""
Then STDOUT should contain:
"""
Run `composer update` from this folder to install or update wp-browser
"""
Then STDOUT should contain:
"""
Run `./vendor/bin/wpcept bootstrap --interactive-mode` to start wp-browser interactive test setup
"""
Should the user instead decide to run the Composer update immediately then the command composer
should be called:
@pathEnv
Scenario: the command will launch composer update if the user wants it to
Given I will answer 'y' to the 'composer update' question
Given I will answer 'n' to the 'wpcept bootstrap' question
When I run `wp scaffold plugin some-plugin --plugin_name="Some Plugin" --plugin_description="Description of the plugin." --plugin_author="Your Name" --plugin_author_uri="http://example.com"`
And I run `wp wpb-scaffold plugin-tests some-plugin` with input
Then 'composer' should have been called
Then STDOUT should contain:
"""
All done
"""
Then STDOUT should not contain:
"""
Run `composer update` from this folder to install or update wp-browser
"""
Then STDOUT should contain:
"""
Run `./vendor/bin/wpcept bootstrap --interactive-mode` to start wp-browser interactive test setup
"""
Mocking the composer
command
Beside testing some expected output the things to note in the scenario above are:
- the tagging of the scenario with
@pathEnv
- the step
Then 'composer' should have been called
The first one leverages the tagging system provided by Behat to “do something” before and after a scenario: specifically here I’m prepending the path to a test data directory to the real PATH
in the first in the FeatureContext
class:
/**
* @BeforeScenario @pathEnv
*/
public function backupPathEnvVar() {
$this->pathBackup = getenv( 'PATH' );
$newPath = 'PATH=' . $this->get_data_dir() . ':' . $this->pathBackup;
putenv( $newPath );
}
/**
* @AfterScenario @pathEnv
*/
public function restorePathEnvVar() {
putenv( 'PATH=' . $this->pathBackup );
}
The data folder contains a simple composer
file that will do nothing but print a message to the output when called:
#!/usr/bin/env php
<?php
echo 'Running mocked composer'; return 0;
The check made in the 'composer' should have been called
step is simply checking for that string in the output (see code here):
$steps->Then( '/^\'([^\']*)\' should have been called$/', function ( $world, $command ) {
checkString( trim( $world->result->stdout, "\n" ), 'Running mocked ' . $command, 'contain' );
} );
The combination of the two is that when the “user”, really a mocked input, answers “y” to the confirmation to run composer update
the script will check for a composer
command in the PATH
and will find the first match in the data folder running the “mock” composer script.