• adamquaile.com

    Running commands between features and scenarios in Behat v3

    When using Behat for any application with more than a small amount of complexity, I often find there's tasks I do time and time again.

    Maybe, at the start of the test suite I need to clear my cache; in a symfony environment this might look something like this:

    php app/console cache:clear --env=test

    Maybe I want to reset the database after each feature, or each scenario...

    php app/console -e test doctrine:database:drop
    php app/console -e test doctrine:database:create
    php app/console -e test doctrine:schema:update 

    Some common ways

    Of course, we have behat hooks like @BeforeSuite, @BeforeScenario, etc.. so we can write code like this in our context files

    <?php
    
        /**
         * @BeforeSuite
         */
        public static function beforeSuite()
        {
            exec('php app/console cache:clear --env=test');
        }

    I've also seen more complex test scripts like this:

    #!/bin/bash
    
    # Some test prep
    php app/console -e test doctrine:database:drop
    php app/console -e test doctrine:database:create
    php app/console -e test doctrine:schema:update
    
    # Maybe run the PHP built-in server, PhantomJS or Selenium...
    ./run-server-or-daemon
    pid=$!
    
    ./bin/behat
    
    # ...and stop it when the tests have finished
    kill $pid
    

    These all work and, depending on your situation, might be exactly what you're looking for. Like any solution to a problem they have their limitations.

    I've been experimenting with on more way:

    The behat command-runner extension

    I've created a proof-of-concept extension to behat which allows us to specify these commands to be run right from inside behat.yml.

    Let me share an extract from one of my recent projects:

    ---
    extensions:
    
        AdamQuaile\Behat\CommandRunnerExtension:
            beforeSuite:
                - rm -rf ./app/cache/test/*
                - php app/console doctrine:database:drop --env=test --force
                - php app/console doctrine:database:create --env=test --force
                - php app/console doctrine:schema:update --env=test --force
                - cp app/test.db3 app/test.initial.db3
            beforeFeature:
                - cp app/test.initial.db3 app/test.db3
    

    What's happening here is fairly self-explanatory. At the beginning of the suite we setup the test database (we're not using any fixtures here, but we could very simply). Also for speed, instead of recreating the database using doctrine each feature, we're backing up and restoring the initial state of a SQLite database.

    It also supports running background tasks, like this:

    ---
    beforeSuite:
      - { command: 'ping example.com', background: true }
    

    The code is hosted over on github and available through composer.

    You'll have to require with @dev as it's still an early proof-of-concept. I'd love feedback on whether this works for your projects, and how it could be better.