Dumping your database on scenario failure in behat

Author: Matt
Tuesday, January 24 2017

Recently while debugging some tests that were failing on our build server, but passing locally, we needed to see the state of the Database for the application when a test had failed - using Behat hooks and the Symfony Process component, here's how we did it.

This is a quick context that we came up with that will dump a database to a file when a scenario in Behat fails.

The filename will be:

behat-scenario-failed-{scenarioTitle}-{dateStamp}.sql

So if I have a scenario that was run:

Scenario: A user can filter results

The filename would be along the lines of:

behat-scenario-failed-A user can filter results-20170124113226.sql

Notes:

  • This context relies on Symfony and the Behat Symfony 2 Extension as it's pulling parameters from the container and using the kernel. If using another framework, substitute as needed (further improvement would be to pass these in as constructor arguments to the context and configure them in behat.yml - homework for you all!)
  • It also stores the dump in the app/logs directory at the minute, this was just because we have the contents of this directory marked as artifacts at the end of a build.

features/bootstrap/App/Context/DbDumpContext.php

<?php

namespace App\Context;

use Behat\Behat\Hook\Scope\AfterScenarioScope;
use Behat\Symfony2Extension\Context\KernelAwareContext;
use Behat\Symfony2Extension\Context\KernelDictionary;
use Symfony\Component\Process\Process;

class DbDumpContext implements KernelAwareContext
{

    use KernelDictionary;

    /**
     * @AfterScenario
     *
     * @param AfterScenarioScope $scope
     */
    public function dumpDatabaseToFile(AfterScenarioScope $scope)
    {
        if ( ! $scope->getTestResult()->isPassed()) {
            $title = $scope->getScenario()->getTitle();
            $processCommand = $this->getProcess($title);

            $sqlDumpProcess = new Process($processCommand);
            $sqlDumpProcess->start();
        }
    }

    /**
     * @param string $scenarioTitle
     *
     * @return string
     */
    private function getDumpFileName($scenarioTitle)
    {
        return sprintf(
            '%s/logs/behat-scenario-failed-%s-%s.sql',
            $this->getKernel()->getRootDir(),
            $scenarioTitle,
            (new \DateTimeImmutable)->format('YmdHis')
        );
    }

    /**
     * @param string $title
     *
     * @return string
     */
    private function getProcess($title)
    {
        $kernel = $this->getKernel();
        $container = $kernel->getContainer();

        return sprintf(
            "mysqldump --user='%s' --password='%s' %s > '%s'",
            $container->getParameter('database_user'),
            $container->getParameter('database_password'),
            $container->getParameter('database_name'),
            $this->getDumpFileName($title)
        );
    }
}

Edit: This has been turned into a little package that we'll be using across our projects when needed - early stages, but you can find it here: vivait/behat-mysql-dump

Ansible and Let's Encrypt for Multi-Tenancy Applications Running PHPUnit with a specific data provider set