highly extensible, highly enjoyable, PHP testing framework.

Peridot is a modern testing tool for PHP 5.4+. It makes testing fun again. It can easily be extended via an awesome event system, allowing developers to create plugins and custom reporters with ease. Follow and contribute on GitHub.

Features

  • Flexible testing interface
  • Event driven plugin architecture
  • Easily add user defined cli options
  • Easily create custom test reporters
  • Pass/fail based on exceptions
  • Mix in functionality via child scopes
  • Create your own testing DSLs

Installation

Peridot can be installed several ways.

Globally (Composer)

$ composer global require peridot-php/peridot:1.0.0

And make sure the .composer/vendor/bin is on your PATH

export PATH="$PATH:$HOME/.composer/vendor/bin"

Locally (per project)

$ composer require peridot-php/peridot:1.0.0

Manual (local)

The latest stable phar can be downloaded here. You can place it anywhere you like.

Manual (global)

Use the following commands to install the peridot command for use anywhere:

$ sudo wget http://peridot-php.github.io/downloads/peridot.phar -O /usr/local/bin/peridot

with curl:

$ sudo curl http://peridot-php.github.io/downloads/peridot.phar -o /usr/local/bin/peridot

then:

$ sudo chmod a+x /usr/local/bin/peridot

Getting Started

<?php //arrayobject.spec.php
describe('ArrayObject', function() {
    beforeEach(function() {
        $this->arrayObject = new ArrayObject(['one', 'two', 'three']);
    });

    describe('->count()', function() {
        it("should return the number of items", function() {
            $count = $this->arrayObject->count();
            assert($count === 3, "expected 3");
        });
    });
});
?>

$ peridot arrayobject.spec.php

  ArrayObject
    ->count()
      ✓ should return the number of items


  1 passing (19 ms)

Peridot DSLs

By default, Peridot uses a BDD style DSL for describing your code's behavior via specs. Here's the breakdown:

  • describe($description, $func)

    This function creates a suite, and is used to organize behaviors. It's fair game to nest these.

  • context($description, $func)

    This function is identical to describe. It is provided to offer additional organization for your specs.

  • beforeEach($setupFunc)

    You can add setup functions that execute for all specs using this function. If you set variables on $this inside of the function body, it will create instance variables that are inherited by specs and suites.

  • afterEach($tearDownFunc)

    You can add teardown functions that execute for all specs using this function.

  • it($description, $func)

    This function creates a single spec and adds it to the suite it is nested in.

Pending Specs

You can make any spec our suite pending by prefixing the function with an x.

<?php
xdescribe("A pending suite", function() {
    xcontext("when using a pending context", function() {
        xit("should have a pending spec", function() {

        });
    });
});
?>

Custom DSLs

You can also create your own testing DSLs! Check out Example: Creating An Acceptance Testing DSL.

Assertions

A Peridot test fails when an exception is thrown. Peridot's default DSL configures the default behavior of PHP's native assert function to throw exceptions, but you could just as easily use an existing matcher library or roll your own.

If you're looking for an existing PHP matcher library, you might want to look at Hamcrest.

Plugins

Peridot plugins are easy to write. Peridot exposes a handful of events that can be hooked into via a configuration file.

Peridot will look for a file called peridot.php in the current working directory, or one can be specified using the --configuration option via the console. The configuration file will be included once, and it should return a function that accepts a single EventEmitter containing ->on() and ->emit() methods.

<?php //peridot.php
return function(EventEmitterInterface $emitter) {
    $emitter->on('test.failed', function($test) {
        //do something with the failed test
    });
}
?>

For more information see Example: Creating An Acceptance Testing DSL.

Coming soon: "Creating The Peridot Code Coverage Plugin."

Reporters

Peridot ships with a spec reporter for printing test results in a hierarchal manner. However, you can easily create your own reporters.

Reporters can be registered via the configuration file like any other plugin through the peridot.reporters event.

Check out Peridot's own peridot.php file for an example of creating a custom reporter.

peridot

Usage:
 peridot [options] [files]

Options:
 --grep (-g)          Run tests matching <pattern> (default: *.spec.php)
 --no-colors (-C)     Disable output colors
 --reporter (-r)      Select which reporter to use (default: spec)
 --bail (-b)          Stop on failure
 --configuration (-c) A php file containing peridot configuration
 --reporters          List all available reporters
 --version (-V)       Display the Peridot version number
 --help (-h)          Display this help message.

-g, --grep

Only run test files whose name matches the provided pattern. The default grep pattern is *.spec.php

-C, --no-colors

Disable colors in output.

-r, --reporter

Select which reporter to use. This is the reporter name registered via ReporterFactory::register.

-b, --bail

Tell peridot to stop running tests as soon as there is a failure.

-c, --configuration

A path to a peridot configuration file. Defaults to getcwd() . '/peridot.php'

--reporters

List available test reporters.

Scopes

Peridot is able to safely use $this inside of test closures because of scopes. Scopes allow us to place state on a test without conflicts.

Scopes also allow mixing in behavior to tests via a powerful concept called child scopes. Consider the following:

<?php //peridot.php
class WebDriverScope extends Scope
{
    protected $driver;
    protected $emitter;

    public function __construct(RemoteWebDriver $driver, EventEmitterInterface $emitter)
    {
        $this->driver = $driver;
        $this->emitter = $emitter;
        $this->emitter->on('runner.end', function() {
            $this->driver->quit();
        });
    }
    public function getPage($url)
    {
        $this->driver->get($url);
    }

    public function findElementById($id)
    {
        return $this->driver->findElement(\WebDriverBy::id($id));
    }
}

return function(EventEmitterInterface $emitter) {
    $driver = RemoteWebDriver::create($host, DesiredCapabilities::chrome());
    $driverScope = new WebDriverScope($driver);

    $emitter->on('suite.start', function($test) use ($driverScope) {
        $test->getScope()->peridotAddChildScope($driverScope);
    });
}
?>

By mixing the WebDriverScope in to our test's scopes, we have made the following possible in our tests:

<?php
describe('The home page', function() {
    it('should have a greeting', function() {
        $this->getPage('http://localhost:4000');
        $greeting = $this->findElementById('greeting');
        assert($greeting->getText() === "Hello", "should be Hello");
    });
});
?>

For a fully functional example of the above, checkout the Peridot scope example on Github.

Between scopes, events, and DSLs - Peridot gives you plenty of opportunities for extension.

Event Reference

The following events are exposed by Peridot's event emitter:

  • peridot.start

    Fires when the Peridot application is constructed. Useful for defining additional CLI arguments and options.

    Arguments:
    Environment $environment

  • peridot.configure

    Fires when the Peridot application is configured.

    Arguments:
    Configuration $configuration

  • peridot.execute

    Fires right before Peridot starts execution. Happens after CLI options are parsed and before specs are loaded.

    Arguments:
    InputInterface $input
    OutputInterface $output

  • peridot.reporters

    Fires when Peridot reporters are registered. Useful for registering additional reporters.

    Arguments:
    InputInterface $input
    ReporterFactory $reporters

  • peridot.load

    Fires right before Peridot starts loading tests. Useful for changing loading behavior.

    Arguments:
    Command $command
    Configuration $configuration

  • peridot.end

    Fires when the Peridot application exits.

    Arguments:
    integer $exitCode
    InputInterface $input
    OutputInterface $output

  • runner.start

    Fires right before the suite runner starts.

    Arguments: No arguments

  • runner.end

    Fires right after the suite runner ends.

    Arguments: No arguments

  • suite.start

    Fires right before a suite runs.

    Arguments:
    Suite $suite

  • suite.end

    Fires right after a suite runs.

    Arguments:
    Suite $suite

  • suite.halt

    When fired, this event signals a suite to stop running.

    Arguments: No arguments

  • test.passed

    Fires when a test passes.

    Arguments:
    Test $test

  • test.pending

    Fires when a test pends.

    Arguments:
    Test $test

  • test.failed

    Fires when a test fails.

    Arguments:
    Test $test
    Exception $exception

  • test.start

    Fires right before a test runs.

    Arguments:
    Test $test

  • test.end

    Fires right after a test runs.

    Arguments:
    Test $test

  • error

    Fires when a PHP error occurs. The standard error arguments passed to a function registered via PHP's native set_error_handler will be passed to this event.

    Arguments:
    integer $errno
    string $errstr
    string $errfile
    string $errline

Example Test Suites

Below are examples of real projects in the wild using Peridot.

Running peridot's tests

Peridot's tests were written using Peridot. After cloning peridot, you can run tests using:

$ bin/peridot specs/