Skip to content
brianium edited this page Nov 8, 2014 · 4 revisions

We really like the BDD style testing language of describe, context, it, beforeEach, and afterEach.

The default DSL (Domain Specific Language) allows us to use that language when testing. However, those functions are just simple wrappers around the Peridot runner context.

Context is the only singleton used in Peridot, and it is used to build our tree of tests. The default DSL uses Context like so:

use Peridot\Runner\Context;

/**
 * Creates a suite and sets it on the suite factory
 *
 * @param string $description
 * @param callable $fn
 */
function describe($description, callable $fn)
{
    Context::getInstance()->addSuite($description, $fn);
}

/**
 * Identical to describe. Useful for test readability
 *
 * @param $description
 * @param callable $fn
 */
function context($description, callable $fn)
{
    describe($description, $fn);
}

/**
 * Create a spec and add it to the current suite
 *
 * @param $description
 * @param $fn
 */
function it($description, callable $fn = null)
{
    Context::getInstance()->addTest($description, $fn);
}

/**
 * Create a pending suite
 *
 * @param $description
 * @param callable $fn
 */
function xdescribe($description, callable $fn)
{
    Context::getInstance()->addSuite($description, $fn, true);
}

/**
 * Create a pending context
 *
 * @param $description
 * @param callable $fn
 */
function xcontext($description, callable $fn)
{
    xdescribe($description, $fn);
}

/**
 * Create a pending spec
 *
 * @param $description
 * @param callable $fn
 */
function xit($description, callable $fn = null)
{
    Context::getInstance()->addTest($description, $fn, true);
}

/**
 * Add a setup function for all specs in the
 * current suite
 *
 * @param callable $fn
 */
function beforeEach(callable $fn)
{
    Context::getInstance()->addSetupFunction($fn);
}

/**
 * Add a tear down function for all specs in the
 * current suite
 *
 * @param callable $fn
 */
function afterEach(callable $fn)
{
    Context::getInstance()->addTearDownFunction($fn);
}

/**
 * Change default assert behavior to throw exceptions
 */
assert_options(ASSERT_WARNING, false);
assert_options(ASSERT_CALLBACK, function ($script, $line, $message, $description) {
    throw new Exception($description);
});

Since we are just delegating to Context, it should be a snap to create our own testing languages.

##Custom DSLs

If we want to use a different DSL, we can use all that Peridot offers to create some fun tools. Say we want to create a feature testing language similar to what Behat does with Gherkin:

Feature("chdir","
    As a PHP user
    I need to be able to change the current working directory",
    function() {

        Scenario(function() {
        
            Given('I am in this directory', function() {
                chdir(__DIR__);
            });

            When('I run getcwd()', function() {
                $this->cwd = getcwd();
            });

            Then('I should get this directory', function() {
                if ($this->cwd != __DIR__) {
                    throw new \Exception("Should be current directory");
                }
            });

        });

    });

We just have to change how Context is used:

use Peridot\Runner\Context;

function Feature($name, $description,  callable $fn) 
{
    $description = 'Feature: ' . $name . $description . "\n";
    Context::getInstance()->addSuite($description, $fn);
}

function Scenario(callable $fn) 
{
    Context::getInstance()->addSuite("Scenario:", $fn);
}

function Given($description, callable $fn)
{
    $test = Context::getInstance()->addTest($description, $fn);
    $test->getScope()->acceptanceDslTitle = "Given";
}

function When($description, callable $fn)
{
    $test = Context::getInstance()->addTest($description, $fn);
    $test->getScope()->acceptanceDslTitle = "When";
}

function Then($description, callable $fn) 
{
    $test = Context::getInstance()->addTest($description, $fn);
    $test->getScope()->acceptanceDslTitle = "Then";
}

##Custom reporters for custom DSLs Custom DSLs are great when the occasion calls for them, and we can make them even more useful by adding reporters for them. We can easily build a FeatureReporter for our custom feature DSL by extending the default SpecReporter:

namespace Peridot\Example;

use Peridot\Core\Test;
use Peridot\Reporter\SpecReporter;

/**
 * The FeatureReporter extends SpecReporter to be more friendly with feature language
 *
 * @package Peridot\Example
 */
class FeatureReporter extends SpecReporter
{
    /**
     * @param Test $test
     */
    public function onTestPassed(Test $test)
    {
        $title = $this->handleGivenWhen($test);

        $this->output->writeln(sprintf(
            "  %s%s %s",
            $this->indent(),
            $this->color('success', $title),
            $this->color('muted', $test->getDescription())
        ));
    }

    /**
     * Given and When don't represent true tests themselves, so we decrement
     * the "passing" count that is reported for each one
     *
     * @param Test $test
     * @return string
     */
    protected function handleGivenWhen(Test $test)
    {
        $scope = $test->getScope();
        $title = $scope->acceptanceDslTitle;
        if (preg_match('/Given|When/', $title)) {
            $this->passing--;
        }
        return $title;
    }
}

Using our peridot.php file, we can register our reporter and DSL by leveraging a couple of events:

//peridot.php
require_once __DIR__ . '/vendor/autoload.php';

return function($emitter) {
    $emitter->on('peridot.configure', function($config) {
        $config->setDsl(__DIR__ . '/src/feature.dsl.php');
        $config->setGrep('*.feature.php');
    });

    $emitter->on('peridot.reporters', function($input, $reporters) {
        $reporters->register('feature', 'A feature reporter', 'Peridot\Example\FeatureReporter');
    }); 
};

Now that we have our custom reporter in place, we can use it like so:

$ vendor/bin/peridot features/ -r feature

And presto!

Peridot acceptance testing

The source code for the examples used on this page can be found here.

Clone this wiki locally