Gherkin syntax support in Codeception 01

Rocking my testing world.

Gherkin support in Codeception

As I read the article tag line it feels more and more awkward.
Beside this detail Codeception beta 2.2 is rolling and it’s introducing, among other things, native Gherkin support.
For anyone involved in a team with varied development skills this is huge.
Until now the closest Codeception could get to “writing tests in plain English” was the cept test format: extremely understandable to me (a developer), readable by non developers but easy to complicate beyond recognition and readability.
Gherkin syntax would allow someone with no development skill, but a knowledge of the framework and the feature at hand, to write a test like this in my environment of choice, WordPress:

Feature: Installation
  In order to use the plugin
  As an administrator
  I need to be able to activate and deactivate the plugin

  Scenario: see the plugin in the plugins admin screen
    Given I am logged in as an administrator
    When I go to the plugins administration page
    Then I should see the "Codeception Beta 22" plugin

  Scenario: activate the plugin
    Given I am logged in as an administrator
    When I go to the plugins administration page
    And I activate the "Codeception Beta 22" plugin
    Then I should see the "Codeception Beta 22" plugin activated

  Scenario: deactivate the plugin
    Given I am logged in as an administrator
    And the "Codeception Beta 22" plugin is activated
    When I go to the plugins administration page
    And I deactivate the "Codeception Beta 22" plugin
    Then I should see the "Codeception Beta 22" plugin deactivated

Now this is easy to read for anyone. Sure the language has to adhere to some formalities but that’s a test, not a narrative book.
The “technicalities of test implementation” are hidden from the tester and fall on the developer to implement and maintain.

I have to try this

While the release it’s still in beta I had to put my hands on it and try to build something so I’ve scaffolded a basic WordPress plugin called “Codeception Beta 22” and wrote the least possible amount of code, the codeception-beta-22/plugin.php file reads:

<?php
/**
 * Plugin Name: Codeception Beta 22
 * Plugin URI: http://theAverageDev.com
 * Description: A WordPress plugin to test Codeception beta 2.2 functionality with WPBrowser.
 * Version: 1.0
 * Author: theAverageDev
 * Author URI: http://theAverageDev.com
 * License: GPL 2.0
 */

Doing nothing but still enough to show up in WordPress plugins list.
Second step was the usual Codeception and wp-browser set up drill to then jump in generate the first feature:

codecept generate:feature acceptance Installation

I’ve filled in the tests/acceptance/Installation.feature file to exactly the text above and had Codeception generate the snippets required to support the steps using

codecept gherkin:snippets acceptance

The CLI output is a long copy-and-paste-able list of methods that should be implemented in the AcceptanceTester class.

Filling in the blanks

Now that Codeception has given me empty methods it’s time to fill those in.
While the way tests are written is different this is still a Codeception acceptance test and I will be using methods dealing with the UI and the database.
I’ve configured the acceptance suite to use the WPBrowser and WPDb modules:

# Codeception Test Suite Configuration

# suite for WordPress acceptance tests.
# perform tests in browser using WPBrowser or WPWebDriver modules.
class_name: AcceptanceTester
modules:
    enabled:
        - WPBrowser
        - \Helper\Acceptance
        - WPDb
    config:
        WPBrowser:
            url: 'http://wp.dev'
            adminUsername: admin
            adminPassword: admin
            adminUrl: /wp-admin
        WPDb:
            dsn: 'mysql:host=127.0.0.1;dbname=wp'
            user: root
            password: root
            dump: tests/_data/dump.sql
            populate: true
            cleanup: true
            url: 'http://wp.dev'
            tablePrefix: wp_

Please note I’m resetting the state of the database before each test setting WPDb::populate and WPDb::cleanup to true; the tests/_data/dump.sql file being a dump of my empty local WordPress installation.
Now that I have the two modules configured it’s time to rebuild the suites

codecept build

and tap into the methods provided by those modules to implement the Gherkin steps.
The build command will regenerate the _generated\AcceptanceTesterActions trait and that will be included by default in the AcceptanceTester class.
After some back and forth here it is the finished code:

<?php
// file AcceptanceTester.php

/**
 * Inherited Methods
 * @method void wantToTest($text)
 * @method void wantTo($text)
 * @method void execute($callable)
 * @method void expectTo($prediction)
 * @method void expect($prediction)
 * @method void amGoingTo($argumentation)
 * @method void am($role)
 * @method void lookForwardTo($achieveValue)
 * @method void comment($description)
 * @method \Codeception\Lib\Friend haveFriend($name, $actorClass = NULL)
 *
 * @SuppressWarnings(PHPMD)
 */
class AcceptanceTester extends \Codeception\Actor
{

    // the ones Codeception built using `codecept build`
    // we can now access the modules provided methods
    use _generated\AcceptanceTesterActions;

    /**
     * @Given I am logged in as an administrator
     */
    public function iAmLoggedInAsAnAdministrator()
    {
        // from WPBrowser module
        $this->loginAsAdmin();
    }

    /**
     * @When I go to the plugins administration page
     */
    public function iGoToThePluginsAdministrationPage()
    {
        // from WPBrowser module
        $this->amOnPluginsPage();
    }

    /**
     * @Then I should see the :pluginName plugin
     */
    public function iShouldSeeThePlugin($pluginName)
    {
        $pluginSlug = $this->buildPluginSlug($pluginName);

        // from WPBrowser module
        $this->seeElement('#the-list  tr[data-slug="' . $pluginSlug . '"]');
    }

    /**
     * @When I activate the :pluginName plugin
     */
    public function iActivateThePlugin($pluginName)
    {
        $pluginSlug = $this->buildPluginSlug($pluginName);

        // from WPBrowser module
        $this->activatePlugin($pluginSlug);
    }

    /**
     * @Then I should see the :pluginName plugin activated
     */
    public function iShouldSeeThePluginActivated($pluginName)
    {
        $pluginSlug = $this->buildPluginSlug($pluginName);

        // from WPBrowser module
        $this->seePluginActivated($pluginSlug);
    }

    /**
     * @Given the :pluginNameOrFullSlug plugin is activated
     */
    public function thePluginIsActivated($pluginNameOrFullSlug)
    {
        // from WPDb module
        $activePlugins = $this->grabOptionFromDatabase('active_plugins');
        $activePlugins = empty($activePlugins) ? [] : $activePlugins;

        // let's make the assumption that either the user passed us the full slug
        // e.g. "some-plugin/main.php"
        // or a plain language name like "Some Plugin"
        $isFullSlug = preg_match('/^.*\\.php$/', $pluginNameOrFullSlug);

        if ($isFullSlug) {
            $pluginFullSlug = [$pluginNameOrFullSlug];
        } else {
            // we'll cover the 80% of the cases: it's either `some-plugin/some-plugin.php`
            // or `some-plugin/plugin.php`
            // something else? User should pass the full slug
            $pluginSlug = $this->buildPluginSlug($pluginNameOrFullSlug);
            $pluginFullSlug = ["{$pluginSlug}/{$pluginSlug}.php", "{$pluginSlug}/plugin.php"];
        }

        if (empty($activePlugins) || !count(array_intersect($pluginFullSlug, $activePlugins))) {
            $activePlugins = array_merge($activePlugins, $pluginFullSlug);
            // again from WPDb module
            $this->haveOptionInDatabase('active_plugins', $activePlugins);
        }
    }

    /**
     * @When I deactivate the :pluginName plugin
     */
    public function iDeactivateThePlugin($pluginName)
    {
        $pluginSlug = $this->buildPluginSlug($pluginName);

        // from WPBrowser module
        $this->deactivatePlugin($pluginSlug);
    }

    /**
     * @Then I should see the :pluginName plugin deactivated
     */
    public function iShouldSeeThePluginDeactivated($pluginName)
    {
        $pluginSlug = $this->buildPluginSlug($pluginName);

        // from WPBrowser module
        $this->seePluginDeactivated($pluginSlug);
    }

    /**
     * @param $pluginName
     * @return string
     */
    protected function buildPluginSlug($pluginName)
    {
        $pluginSlug = implode('-', array_map('strtolower', preg_split('/[-_\\s]/', $pluginName)));
        return $pluginSlug;
    }
}

Running the tests now will see them pass: Passing Gherkin syntax tests

and running codecept gherkin:steps again will show an informative screen: Codeception Gherkin steps report

Next

While the possibility is nice I would not like to write all this code every time: a porting over to the new Gherkin syntax of WPBrowser and WPDb methods is in order.