BDD Testing Framework

Installs: 6

Dependents: 0

Stars: 5

Watchers: 3

Language: PHP

v1.3.0 2013-12-12 10:47 UTC

README

A PHP Testing Framework

describe("Phoebe", function() {
    it("makes testing PHP awesome!", function() {
        expect(yourCode)->toBeLotsBetter();
    });
});

Phoebe is a Behavior Driven Development testing framework for PHP, strongly inspired by Jasmine. It aims to make testing in PHP a lot easier by removing the need to extend test classes to create scenario. With Phoebe, tests are just plain functions

Adding Phoebe to your project

Add this line to your project's composer.json

{
    "require-dev": {
        "bdelespierre/phoebe": "dev-master"
    },
}

Then run composer install --dev. Once done, build phoebe.phar with `php /vendor/bdelespierre/phoebe/build/build.php".

Try it:

$ php /vendor/bdelespierre/phoebe/demo.php --display-passed

The demo.php file contains a lot of examples on how to use bdelespierre\phoebe. When you run it, don't forget the --display-passed option, By default Phoebe will only display errors and failed suites.

Let's test !

Include phoebe.phar and write your tests in a human familliar syntax; simply nest describe and it blocks to create you scenario.

<?php
require 'phar://path/to/phoebe/build/phoebe.phar';

describe("A suite", function() {
    it("contains spec with an expectation", function() {
        expect(true)->toBe(true);
    });
});

describe("A suite is just a function", function() {
    it("and so is a spec", function() {
        $a = true;
        expect($a)->toBe(true);
    });
});

describe("The 'toBe' matcher compares with ===", function() {
    it("and has a positive case ", function() {
        expect(true)->toBe(true);
    });
    it("and can have a negative case", function() {
        expect(false)->not()->toBe(true);
    });
});

Result:

A suite
  [OK] contains spec with an expectation

A suite is just a function
  [OK] and so is a spec

The 'toBe' matcher compares with ===
  [OK] and has a positive case 
  [OK] and can have a negative case

Expectations

In Phoebe, we call an unit an expectation. Because we focus on behavior, we expect a value (or a function/method call) to match our specifications. This is achived with the expect block.

An expect block takes a value (or a Callable) as only parameter and returns an object used to call matchers. You may call the not() method to negate the expectation result.

// expecting false to be true
expect(false)->toBeTrue();

// expecting the Foo::bar call not to throw
expect('Foo::bar')->call()->not()->toThrow();

Default matchers

The default matchers are pretty straightforward:

  • toBe($value)
  • toBeInstanceOf($classname)
  • toEqual($value)
  • toMatch($pattern)
  • toBeTrue()
  • toBeTruthy()
  • toBeFalse()
  • toBeFalsy()
  • toContain()
  • toBeNull()
  • toBeEmpty()
  • toBeGreaterThan($value)
  • toBeLessThan($value)
  • toBeCloseTo($value,$precision)
  • toBeBetween($from,$to)
  • toBeIn(array $values)
  • toThrow($exception)

User defined matchers

Phoebe allows you to register your own matchers in a pretty simple way.

matcher('toContainString', function ($expected, $obtained) {
    return strpos($obtained, $expected) !== false;
});

expect('Phoebe is cute!')->toContainString('cute');

The only rule to observe is: last parameter of a matcher must be the obtained value.

beforeEach and afterEach

Like most other testing frameworks, Phoebe support a way to execute pieces of code before and after each test block (commonly called setup and teardown). Each describe block may have one before function and one after function, they will be executed around each child block.

<?php
require 'phar://path/to/phoebe/build/phoebe.phar';

describe("A spec (with setup and tear-down)", function() {
    $foo;

    beforeEach(function() use (&$foo) {
        $foo = 0;
        $foo += 1;
    });

    afterEach(function() use (&$foo) {
        $foo = 0;
    });

    it("is just a function, so it can contain any code", function() use (&$foo) {
        expect($foo)->toEqual(1);
    });

    it("can have more than one expectation", function() use (&$foo) {
        expect($foo)->toEqual(1);
        expect(true)->toEqual(true);
    });
});

This behavior guarantee the data isolation between blocks as each it or describe execution is independent from its siblings.

Spies

Phoebe supports spies from version 2.3. A spy is a quick and simple way to mock interface without having to extend it. It works using The AOP extension, you can install it with

$ sudo pecl install AOP

A spy can:

  • track a method execution
  • track arguments of each call
  • mock a method's return using a value or a function
  • track a method execution without blocking it

An mix all of the above.

When you write your specs, you can verify that the spies are available using the canSpy() shorthand. You may also use getSpy() to access each call and its arguments, and spyOff() to turn off spying on a method (don't forget that you may spy on static method as well but you'll have to release them manually or the spy will be executed on every following specification).

<?php
require 'phar://path/to/phoebe/build/phoebe.phar';

canSpy() && describe("A spy", function () {

    beforeEach(function () use (& $foo) {
        $foo = new Foo;

        spyOn($foo, 'setBar');

        $foo->setBar(123);
        $foo->setBar(456, 'another param');
    });

    it("tracks that the spy was called", function () use (& $foo) {
        expect(array($foo, 'setBar'))->toHaveBeenCalled();
    });

    it("tracks its number of calls", function() use (& $foo) {
        expect(count(getSpy($foo, 'setBar')->calls))->toEqual(2);
    });

    it("traks all the argumenst of its calls", function () use (& $foo) {
        expect(array($foo, 'setBar'))->toHaveBeenCalledWith(123);
        expect(array($foo, 'setBar'))->toHaveBeenCalledWith(456, 'another param');
    });

    it("allows access to the most recent call", function() use (& $foo) {
        expect(getSpy($foo, 'setBar')->mostRecentCall->args[0])->toEqual(456);
    });

    it("allows access to other calls", function() use (& $foo) {
        expect(getSpy($foo, 'setBar')->calls[0]->args[0])->toEqual(123);
    });

    it("stops all execution on a function", function() use (& $foo) {
        expect($foo->bar)->toBeNull();
    });

});

See the demo.php file for more.

Events

Phoebe allows you to listen to specific events which are being fired during the scenario execution and to register custom handlers to them. Available events are:

  • start (fired when a describe or it block starts)
  • complete
  • break
  • error
  • exception

Listening to events is being done using the bind and unbind functions, the handlers are, as always, plain functions. You can even bind multiple events on the same handler.

<?php
require_once "build/phoebe.phar";

$buffer = "";

bind('error,exception', function ($event) {
    echo "Error occured: $event->errstr in $event->errfile at $event->errline\n";
});

// display an handy .....F...F...F. string upon unit resolution
bind('complete', function ($event) use (& $buffer) {
    if ($event->getSource() instanceOf Phoebe\Unit)
        $buffer .= $event->result ? '.' : 'F';
});

describe("A suite", function() {
    it("contains spec with an expectation", function() {
        expect(true)->toBe(true);
    });
});

describe("A suite is just a function", function() {
    it("and so is a spec", function() {
        $a = true;
        expect($a)->toBe(true);
    });
});

describe("The 'toBe' matcher compares with ===", function() {
    it("and has a positive case ", function() {
        expect(true)->toBe(true);
    });
    it("and can have a negative case", function() {
        expect(false)->not()->toBe(true);
    });
});

// prints a quick test summary
echo "Summary:\n", $buffer, "\n";