Describe the core domain of Magento using BDD tools

Posted February 8, 2016

Previsoulsy I covered how to use PHPSpec to describe behaviour at a low level or Unit test level. Today lets take a look at what it takes to use Behat and actually describe user behaviour through the site.

Before we start I recomend reading Modelling By Example by Everzet. Its a really great article about BDD and DDD.

So the first thing we need to do is install Behat in our project. Yet again open up the main composer.json manifest file and this time in the require-dev section add in "behat/behat": "3.*", Save the file and run composer install. Assuming all went well we should be able to run ./bin/behat and get an error message about missing feature definitions. This is OK we will be fixing this soon.

As we are going to be describing both the core domain and the UI or CLI we want to setup some configuration for Behat. To do this we need to create a behat.yml file in our project with the following settings

default:
    suites:
        core_features:
            paths:    [ %paths.base%/features/core ]()
            contexts: [ CoreDomainContext ]()
        cli_features:
            paths:    [ %paths.base%/features/cli ]()
            contexts: [ CliContext ]()

Great so what are we doing here ? Well we want to enable suites so that we can have different feature files and context for our Core Domain and our CLI application. In the future we will also have a UI suite for when we are testing the website itself. Then for each of the suites we just want to specify where the feature files will be located and what context file to use.

Save this file and run ./bin/behat --init this will go ahead and create us all the required folders that we need.

Now in this guide we wont be writing any new code as we did this previously in the PHPSpec Guide so if you do not have the source files generated already I would go back and do this first. I know its bad form to write tests after the fact but meh this is my blog.

Lets create our first scenario. Under features/core create a new file for Hello-World-module.feature with the following content

Feature: The Hello World module should return Hello World when ran
  In order to learn Magento 2
  As a developer excited to learn more
  I want to get feedback when I run my CLI module

  Scenario: The hello World module will return a string
    Given My module exists
    When I run the Hello World module
    Then I should be presented with "Hello World"

What we are saying here is that when we call our Core Domain objects directly then we should be returned the string values. Now we are not executing the CLI scripts in this scenario, We will be calling the objects directly and this is made possible because we are de coupling from Magento 2.

If you now open features/bootstrap/CoreDomainContext.php and replace the contents with

\<?php

use Behat\Behat\Tester\Exception\PendingException;
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;

/** 
 * Defines application features from the specific context.
 */
class CoreDomainContext implements Context, SnippetAcceptingContext
{
    private $helloWorldModule;

    private $output;

    public function __construct()
    {
        $this-\>helloWorldModule = new \Jcowie\HelloWorld\Model\HelloWorld();
    }

    /**
     [email protected] My module exists
     */
    public function myModuleExists()
    {
        return true;
    }

    /** 
     [email protected] I run the Hello World module
     */
    public function iRunTheHelloWorldModule()
    {
        $this-\>output = $this-\>helloWorldModule-\>sayHello();
    }

    /**
     [email protected] I should be presented with :argument
     */
    public function iShouldBePresentedWith($argument)
    {
        expect($this-\>output)-\>toBe($argument);
    }
}

What are we doing here ? Well to start with we create a new instance of our HelloWorld model class as assign it to a private variable that we can inspect and use in future steps. In the When step we call the sayHello method and store the output in a variable again. Finally we make our assertion of the output to ensure the string returned is what we expect. Pretty simple but it shows how powerful PHPSpec and Behat can be when used in combination as well as how we now have the ubiquitous language that we used to describe the core domain with.

If you save the file and run ./bin/behat you will see that the tests are all green now and it ran really fast.

Writing a scenario for the CLI

Great so now we know that our core domain behaves as we expect lets create a functional test to make sure when we run the Magento 2 CLI command our new command is listed.

This time create a new file under features/Cli/Hello-world-cli-module.feature with the following content

Feature: The Hello World module should return Hello World when ran
  In order to learn Magento 2
  As a developer excited to learn more
  I want to get feedback when I run my CLI module

  Scenario: The hello World module will return a string
    Given My module exists
    When I run "bin/magento jcowie:helloworld" from the command line
    Then I should see "Hello World" returned

You will notice it looks similar to the core domain senario but this time we are actually running bin/magento to invoke Magento 2 and our command.

Lets replace CliContext.php with the following:

\<?php

use Behat\Behat\Tester\Exception\PendingException;
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;
use Symfony\Component\Process\PhpExecutableFinder;
use Symfony\Component\Process\Process;

/** 
 * Defines application features from the specific context.
 */
class CliContext implements Context, SnippetAcceptingContext
{
    private $output;

    /**
     [email protected] My module exists
     */
    public function myModuleExists()
    {
        if (file_exists("src/Jcowie")) {
            return true;
        } else {
            throw new PendingException("Folder not found for module");
        }
    }

    /** 
     [email protected] I run :command from the command line
     */
    public function iRunFromTheCommandLine($command)
    {
        $process = new Process(getcwd() . "/".  $command);
        $process-\>run();

        $this-\>output = $process-\>getOutput();
    }

    /**
     [email protected] I should see :output returned
     */
    public function iShouldSeeReturned($output)
    {
        expect(trim($this-\>output))-\>toBe($output);
    }
}

Im using the Symfony component Process to actually run the CLI commands so in the When I create a new process and store the output in a private variable. Then in the Then step I use the expect lib to ensure the output is what I want.

Conclusion

Although the examples here were very simple, you can see how easy it is to use Behat to test both the core domain and the CLI. In the next guide I will go through how we can connect to the database with Behat and how we can setup and configure Selenium to start testing the web application.

You may also find these related posts interesting: Developer Book Club All hail Xdebug and lets let var dump die A new look and a cleanup of content plus good bye github pages. Docker and Docker Sync