Having a go at creating a Behat 3 extension

Ever since I got accepted to do a tutorial on Test legacy apps with Behat at the PHPNW15 conference, I’ve been meaning to look into creating custom extensions for Behat.

I didn’t have enough time to research into this while preparing for the tutorial, so left it in my todo list in Trello.

During the PHPNW15 long weekend (Friday - Monday), at some point over lunch I was at the table where Matt Brunt (@themattbrunt) and Ciaran McNulty (@ciaranmcnulty) were having a conversation about this and Ciaran said (paraphrasing) “In order to be able to write an extension, you really need to understand how Behat works.”

So a few months later, sleeves up and I went into my vendor/bin/behat and started looking (and poking) around.

Behat is a great application. Nice and clean code. And everything happens by magic - well, nearly. :)

But… I won’t be going into that.

I ended up taking a shortcut. I found a great article by its creator Konstantin Kudryashov (https://gist.github.com/everzet/9888598), and spent a lot of time looking into two existing extensions:

I’ve actually used mostly the structure of the Laravel extension.

The most important thing to keep in mind is that everything is being driven by Interfaces.

Following the structure of the Laravel extension, we have three main folders:

  • Context
  • Driver (won’t cover this here)
  • ServiceContainer

ServiceContainer/BehatExtension.php

This class implements Behat\Behat\Context\ServiceContainer\ContextExtension and requires/implements the following methods:

  • getConfigKey() - The extension config key
  • initialize(ExtensionManager $extensionManager) - used to hook into the configuration of other extensions e.g. Mink
  • process(ContainerBuilder $container) - You can modify the container here before it is dumped to PHP code.
  • configure(ArrayNodeDefinition $builder) - Setups configuration for the extension.
  • load(ContainerBuilder $container, array $config) - Loads extension services into temporary container.

configure(ArrayNodeDefinition $builder)

This method enables you to set the configuration for the extension. The parameters you need and their default values. For the Slim 3 extension, I used the config_file location (settings.php) which together with the dependencies_file is pretty much all I need to load the Slim/App and populate the DI container.

Adding the parameters in behat.yml

See the implementation on Github: https://github.com/pavlakis/slim-behat-extension/blob/master/src/ServiceContainer/BehatExtension.php#L42

load(ContainerBuilder $container, array $config)

This method lets you pass the configuration to your service in order to load it.

The $config array gives you access to extension configuration parameters (in behat.yml) e.g. $config['config_file'] and $config['dependencies_file']

We can use those values to load our Service through the $container.

The ContainerBuilder is used to:

  • Create an instance of the Service and pass it to the builder using $container->set(..)
  • Add a Definition containing the:
    * The Service and its link to its own extension class implementing ContextInitializer
    * A tag definition for the Service
  • Context/KernelAwareInitializer.php

    This class implements Behat\Behat\Context\Initializer\ContextInitializer and requires/implements the following methods:

    initializeContext(Context $context) - Initializes provided context.

    The constructor makes the Service/Kernel available. In the initializeContext(Context $context) method we check if the Context passed is an instance of the context we are expecting (in this case Pavlakis\Slim\Behat\Context\KernelAwareContext which also implements Behat\Behat\Context\Context - but it is specific to our extension) and if it is, we add our Service to the Context.

    https://github.com/pavlakis/slim-behat-extension/blob/master/src/Context/KernelAwareInitializer.php#L50

    Pavlakis\Slim\Behat\Context\App.php (Trait)

    This class is a Trait. And very important.

    It makes the app available through the FeatureContext class by just calling $this->app

    To use it, implement Pavlakis\Slim\Behat\Context\KernelAwareContext

    And that’s it. These are the points that I spent most time on. Hope it helps.

    This extension is available available through packagist: https://packagist.org/packages/pavlakis/slim-behat-extension

    And Github:
    https://github.com/pavlakis/slim-behat-extension