Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/danielstjules/pho
BDD test framework for PHP
https://github.com/danielstjules/pho
bdd php testing testing-tools
Last synced: 19 days ago
JSON representation
BDD test framework for PHP
- Host: GitHub
- URL: https://github.com/danielstjules/pho
- Owner: danielstjules
- License: mit
- Created: 2013-10-11T01:55:28.000Z (about 11 years ago)
- Default Branch: master
- Last Pushed: 2017-04-24T04:47:38.000Z (over 7 years ago)
- Last Synced: 2024-10-11T22:54:07.740Z (2 months ago)
- Topics: bdd, php, testing, testing-tools
- Language: PHP
- Size: 255 KB
- Stars: 284
- Watchers: 19
- Forks: 14
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
- awesome-php - Pho - Another behavior-driven development testing framework. (Table of Contents / Testing)
- awesome-projects - Pho - Another behaviour driven development testing framework. (PHP / Testing)
- awesome-php-cn - Pho - 另一个行为驱动开发测试框架. (目录 / 测试 Testing)
README
![pho](http://danielstjules.com/github/pho-logo.png)
BDD test framework for PHP, inspired by Jasmine and RSpec. Features a familiar
syntax, and a watch command to automatically re-run specs during development.
It can also be extended with custom matchers and reporters.[![Build Status](https://travis-ci.org/danielstjules/pho.svg?branch=master)](https://travis-ci.org/danielstjules/pho)
* [Installation](#installation)
* [Usage](#usage)
* [Writing Specs](#writing-specs)
* [Running Specs](#running-specs)
* [Expectations/Matchers](#expectationsmatchers)
* [Custom Matchers](#custom-matchers)
* [Reporters](#reporters)
* [Mocking](#mocking)
* [Namespace](#namespace)## Installation
The following instructions outline installation using Composer. If you don't
have Composer, you can download it from [http://getcomposer.org/](http://getcomposer.org/)
If you're new to composer, make sure to add the vendor bin to your PATH:``` bash
# Append the following to your profile file, for example in ~/.profile
export PATH=$HOME/.composer/vendor/bin:$PATH
```To install pho, run:
``` bash
composer global require danielstjules/pho
```## Usage
``` bash
Usage: pho [options] [files]Options
-a --ascii Show ASCII art on completion
-b --bootstrap Bootstrap file to load
-f --filter Run specs containing a pattern
-h --help Output usage information
-n --namespace Only use namespaced functions
-r --reporter Specify the reporter to use
-s --stop Stop on failure
-v --version Display version number
-w --watch Watch files for changes and rerun specs
-C --no-color Disable terminal colors
```## Writing Specs
Pho exposes a DSL for organizing and writing your tests, which includes the
following functions: `describe`, `context`, `it`, `before`, `after`, `beforeEach`,
`afterEach` and `expect`. Equivalent functions for disabling specs and suites
also exist via `xdescribe`, `xcontext` and `xit`.To create a suite, `describe` and `context` can be used by passing them a
string and function. Both are interchangeable, though context is more often
nested in a describe to group some set of behaviour. `it` is then used to create
a spec, or test.A spec may contain multiple expectations or assertions, and will pass so long
as all assertions pass and no exception is uncaught. For asserting values in pho,
`expect` can be used. The function accepts the value to be tested, and may be
chained with a handful of matchers.``` php
toBe(true);
});it('can have specs that fail', function() {
expect(false)->not()->toBe(false);
});it('can have incomplete specs');
});
```![intro-screenshot](http://danielstjules.com/github/pho-intro.png)
Objects may be passed between suites and specs with php's `use` keyword. Here's
an example:``` php
describe('Example', function() {
$object = new stdClass();
$object->name = 'pho';context('name', function() use ($object) {
it('is set to pho', function() use ($object) {
expect($object->name)->toBe('pho');
});
});
});
```Things can get a bit verbose when dealing with multiple objects that need to be
passed into closures with `use`. To avoid such long lists of arguments, `$this`
can be used to set and retrieve values between suites and specs.``` php
describe('SomeClass', function() {
$this->key1 = 'initialValue';
$this->key2 = 'initialValue';context('methodOne()', function() {
$this->key1 = 'changedValue';it('contains a spec', function() {
expect($this->key1)->toBe('changedValue');
expect($this->key2)->toBe('initialValue');
});
});context('methodTwo()', function() {
it('contains another spec', function() {
expect($this->key1)->toBe('initialValue');
expect($this->key2)->toBe('initialValue');
});
});
});
```Hooks are available for running functions as setups and teardowns. `before` is
ran prior to any specs in a suite, and `after`, once all in the suite have been
ran. `beforeEach` and `afterEach` both run their closures once per spec. Note
that `beforeEach` and `afterEach` are both stackable, and will apply to specs
within nested suites. Furthermore, Global hooks may be defined in your bootstrap
file. For example, an afterEach hook in a bootstrap file will run after every
test in your suite.``` php
describe('Suite with Hooks', function() {
$this->count = 0;beforeEach(function() {
$this->count = $this->count + 1;
});it('has a count equal to 1', function() {
expect($this->count)->toEqual(1);
// A single beforeEach ran
});context('nested suite', function() {
beforeEach(function() {
$this->count = $this->count + 1;
});it('has a count equal to 3', function() {
expect($this->count)->toEqual(3);
// Both beforeEach closures incremented the value
});
});
});
```## Running Specs
By default, pho looks for specs in either a `test` or `spec` folder under the
working directory. It will recurse through all subfolders and run any files
ending with `Spec.php`, ie: userSpec.php. Furthermore, continuous testing is as
easy as using the `--watch` option, which will monitor all files in the path for
changes, and rerun specs on save.![watch](http://danielstjules.com/github/pho-watch.gif)
## Expectations/Matchers
#### Type Matching
``` php
expect('pho')->toBeA('string');
expect(1)->notToBeA('string');
expect(1)->not()->toBeA('string');expect(1)->toBeAn('integer'); // Alias for toBeA
expect('pho')->notToBeAn('integer');
expect('pho')->not()->toBeA('integer');
```#### Instance Matching
``` php
expect(new User())->toBeAnInstanceOf('User');
expect(new User())->not()->toBeAnInstanceOf('Post');
expect(new User())->notToBeAnInstanceOf('Post');
```#### Strict Equality Matching
``` php
expect(true)->toBe(true);
expect(true)->not()->toBe(false);
expect(true)->notToBe(false);expect(['foo'])->toEqual(['foo']); // Alias for toBe
expect(['foo'])->not()->toEqual(true);
expect(['foo'])->notToEqual(true);
```#### Loose Equality Matching
``` php
expect(1)->toEql(true);
expect(new User('Bob'))->not()->ToEql(new User('Alice'))
expect(new User('Bob'))->notToEql(new User('Alice'))
```#### Length Matching
``` php
expect(['tdd', 'bdd'])->toHaveLength(2);
expect('pho')->not()->toHaveLength(2);
expect('pho')->notToHaveLength(2);expect([])->toBeEmpty();
expect('pho')->not()->toBeEmpty();
expect('pho')->notToBeEmpty();
```#### Inclusion Matching
``` php
expect('Spectacular!')->toContain('Spec');
expect(['a', 'b'])->not()->toContain('c');
expect(['a', 'b'])->notToContain('c');expect('testing')->toContain('test', 'ing'); // Accepts multiple args
expect(['tdd', 'test'])->not()->toContain('bdd', 'spec');
expect(['tdd', 'test'])->notToContain('bdd', 'spec');expect(['name' => 'pho'])->toHaveKey('name');
expect(['name' => 'pho'])->not()->toHaveKey('id');
expect(['name' => 'pho'])->notToHaveKey('id');
```#### Pattern Matching
``` php
expect('tdd')->toMatch('/\w[D]{2}/i');
expect('pho')->not()->toMatch('/\d+/');
expect('pho')->notToMatch('/\d+/');expect('username')->toStartWith('user');
expect('spec')->not()->toStartWith('test');
expect('spec')->notToStartWith('test');expect('username')->toEndWith('name');
expect('spec')->not()->toEndWith('s');
expect('spec')->notToEndtWith('s');
```#### Numeric Matching
``` php
expect(2)->toBeGreaterThan(1);
expect(2)->not()->toBeGreaterThan(2);
expect(1)->notToBeGreaterThan(2);expect(2)->toBeAbove(1); // Alias for toBeGreaterThan
expect(2)->not()->toBeAbove(2);
expect(1)->notToBeAbove(2);expect(1)->toBeLessThan(2);
expect(1)->not()->toBeLessThan(1);
expect(2)->notToBeLessThan(1);expect(1)->toBeBelow(2); // Alias for toBeLessThan
expect(1)->not()->toBeBelow(1);
expect(2)->notToBeBelow(1);expect(1)->toBeWithin(1, 10); // Inclusive
expect(-2)->not()->toBeWithin(-1, 0);
expect(-2)->notToBeWithin(-1, 0);
```#### Print Matching
``` php
$callable = function() {
echo 'test'
};expect($callable)->toPrint('test');
expect($callable)->not()->toPrint('testing');
expect($callable)->notToPrint('testing');
```#### Exception Matching
``` php
$callable = function() {
throw new Custom\Exception('error!');
};expect($callable)->toThrow('Custom\Exception');
expect($callable)->not()->toThrow('\ErrorException');
expect($callable)->notToThrow('\ErrorException');
```## Custom Matchers
Custom matchers can be added by creating a class that implements
`pho\Expectation\Matcher\MatcherInterface` and registering the matcher with
`pho\Expectation\Expectation::addMatcher()`. Below is an example of a basic
matcher:``` php
namespace example;use pho\Expectation\Matcher\MatcherInterface;
class ExampleMatcher implements MatcherInterface
{
protected $expectedValue;public function __construct($expectedValue)
{
$this->expectedValue = $expectedValue;
}public function match($actualValue)
{
return ($actualValue === $this->expectedValue);
}public function getFailureMessage($negated = false)
{
if (!$negated) {
return "Expected value to be {$this->expectedValue}";
} else {
return "Expected value not to be {$this->expectedValue}";
}
}
}
```Registering it:
``` php
use pho\Expectation\Expectation;// Register the matcher
Expectation::addMatcher('toHaveValue', '\example\ExampleMatcher');
```And that's it! You would now have access to the following:
``` php
expect($actual)->toHaveValue($expected);
expect($actual)->not()->toHaveValue($expected);
expect($actual)->notToHaveValue($expected);
```## Reporters
#### dot (default)
```
$ pho --reporter dot exampleSpec.php.FI
Failures:
"A suite can have specs that fail" FAILED
/Users/danielstjules/Desktop/exampleSpec.php:9
Expected false not to be falseFinished in 0.00125 seconds
3 specs, 1 failure, 1 incomplete
```#### spec
```
$ pho --reporter spec exampleSpec.phpA suite
contains specs with expectations
can have specs that fail
can have incomplete specsFailures:
"A suite can have specs that fail" FAILED
/Users/danielstjules/Desktop/exampleSpec.php:9
Expected false not to be falseFinished in 0.0012 seconds
3 specs, 1 failure, 1 incomplete
```#### list
```
$ pho --reporter list exampleSpec.phpA suite contains specs with expectations
A suite can have specs that fail
A suite can have incomplete specsFailures:
"A suite can have specs that fail" FAILED
/Users/danielstjules/Desktop/exampleSpec.php:9
Expected false not to be falseFinished in 0.0012 seconds
3 specs, 1 failure, 1 incomplete
```## Mocking
Pho doesn't currently provide mocks/stubs out of the box. Instead, it's suggested
that a mocking framework such as [prophecy](https://github.com/phpspec/prophecy)
or [mockery](https://github.com/padraic/mockery) be used.*Note*: Tests can be failed from a test hook. If you need to check mock object
expectations after running a spec, you can do so from an afterEach hook.```php
describe('A suite', function() {
afterEach(function() {
Mockery::close();
});it('should check mock object expectations', function() {
$mock = Mockery::mock('simplemock');
$mock->shouldReceive('foo')->with(5)->once()->andReturn(10);
expect($mock->foo(5))->toBe(10);
});
});
```## Namespace
If you'd rather not have pho use the global namespace for its functions, you
can set the `--namespace` flag to force it to only use the pho namespace. This
will be a nicer alternative in PHP 5.6 with
[https://wiki.php.net/rfc/use_function](https://wiki.php.net/rfc/use_function)``` php
pho\describe('A suite', function() {
pho\it('contains specs with expectations', function() {
pho\expect(true)->toBe(true);
});pho\it('can have specs that fail', function() {
pho\expect(false)->not()->toBe(false);
});
});
```