All Projects → danielstjules → Pho

danielstjules / Pho

Licence: mit
BDD test framework for PHP

Projects that are alternatives of or similar to Pho

Karate
Test Automation Made Simple
Stars: ✭ 5,497 (+1815.33%)
Mutual labels:  testing-tools, bdd
bat
Gherkin based DSL for testing HTTP APIs via Cucumber.JS
Stars: ✭ 30 (-89.55%)
Mutual labels:  bdd, testing-tools
Mocha
☕️ simple, flexible, fun javascript test framework for node.js & the browser
Stars: ✭ 20,986 (+7212.2%)
Mutual labels:  testing-tools, bdd
Serenity Js
A next generation, full-stack acceptance testing framework optimised for collaboration, speed and scale!
Stars: ✭ 346 (+20.56%)
Mutual labels:  testing-tools, bdd
karate-runner
VSCode Extension for Karate
Stars: ✭ 23 (-91.99%)
Mutual labels:  bdd, testing-tools
Gauge
Light weight cross-platform test automation
Stars: ✭ 2,622 (+813.59%)
Mutual labels:  testing-tools, bdd
Phpspec Code Coverage
Generate Code Coverage reports for PhpSpec tests
Stars: ✭ 59 (-79.44%)
Mutual labels:  testing-tools, bdd
showroom
Universal development and automated test environment for web components
Stars: ✭ 89 (-68.99%)
Mutual labels:  bdd, testing-tools
kheera-testrunner-android
BDD Framework for Android
Stars: ✭ 18 (-93.73%)
Mutual labels:  bdd, testing-tools
karate
Test Automation Made Simple
Stars: ✭ 6,384 (+2124.39%)
Mutual labels:  bdd, testing-tools
orion
A next-generation testing tool. Orion provides a powerful DSL to write and automate your acceptance tests
Stars: ✭ 40 (-86.06%)
Mutual labels:  bdd, testing-tools
behave-restful
BDD Framework to Test REST Services and APIs
Stars: ✭ 47 (-83.62%)
Mutual labels:  bdd, testing-tools
hitchstory
Type-safe, StrictYAML based BDD framework for python.
Stars: ✭ 24 (-91.64%)
Mutual labels:  bdd
gomatch
Library created for testing JSON against patterns.
Stars: ✭ 41 (-85.71%)
Mutual labels:  bdd
bron
🏃‍♂️ Fast & tiny test runner for Node.js
Stars: ✭ 17 (-94.08%)
Mutual labels:  testing-tools
super-powered-api-testing
Comparisons of powerful API testing tools
Stars: ✭ 25 (-91.29%)
Mutual labels:  testing-tools
Recon My Way
This repository created for personal use and added tools from my latest blog post.
Stars: ✭ 271 (-5.57%)
Mutual labels:  testing-tools
puppeteer-screenshot-tester
Small library that allows us to compare screenshots generated by puppeteer in our tests.
Stars: ✭ 50 (-82.58%)
Mutual labels:  testing-tools
grpcman
A grpc testing tool based on Electron & Vue.js & Element-UI
Stars: ✭ 22 (-92.33%)
Mutual labels:  testing-tools
Awesome Unit Testing Swift
A curated collection of awesome blog articles, books, talks, podcasts, tools/frameworks and examples.
Stars: ✭ 272 (-5.23%)
Mutual labels:  testing-tools

pho

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

Installation

The following instructions outline installation using Composer. If you don't have Composer, you can download it from http://getcomposer.org/ If you're new to composer, make sure to add the vendor bin to your PATH:

# Append the following to your profile file, for example in ~/.profile
export PATH=$HOME/.composer/vendor/bin:$PATH

To install pho, run:

composer global require danielstjules/pho

Usage

Usage: pho [options] [files]

Options

   -a   --ascii                     Show ASCII art on completion
   -b   --bootstrap   <bootstrap>   Bootstrap file to load
   -f   --filter      <pattern>     Run specs containing a pattern
   -h   --help                      Output usage information
   -n   --namespace                 Only use namespaced functions
   -r   --reporter    <name>        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

describe('A suite', function() {
    it('contains specs with expectations', function() {
        expect(true)->toBe(true);
    });

    it('can have specs that fail', function() {
        expect(false)->not()->toBe(false);
    });

    it('can have incomplete specs');
});

intro-screenshot

Objects may be passed between suites and specs with php's use keyword. Here's an example:

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.

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.

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

Expectations/Matchers

Type Matching

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

expect(new User())->toBeAnInstanceOf('User');
expect(new User())->not()->toBeAnInstanceOf('Post');
expect(new User())->notToBeAnInstanceOf('Post');

Strict Equality Matching

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

expect(1)->toEql(true);
expect(new User('Bob'))->not()->ToEql(new User('Alice'))
expect(new User('Bob'))->notToEql(new User('Alice'))

Length Matching

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

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

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

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

$callable = function() {
  echo 'test'
};

expect($callable)->toPrint('test');
expect($callable)->not()->toPrint('testing');
expect($callable)->notToPrint('testing');

Exception Matching

$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:

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:

use pho\Expectation\Expectation;

// Register the matcher
Expectation::addMatcher('toHaveValue', '\example\ExampleMatcher');

And that's it! You would now have access to the following:

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 false

Finished in 0.00125 seconds

3 specs, 1 failure, 1 incomplete

spec

$ pho --reporter spec exampleSpec.php

A suite
    contains specs with expectations
    can have specs that fail
    can have incomplete specs

Failures:

"A suite can have specs that fail" FAILED
/Users/danielstjules/Desktop/exampleSpec.php:9
Expected false not to be false

Finished in 0.0012 seconds

3 specs, 1 failure, 1 incomplete

list

$ pho --reporter list exampleSpec.php

A suite contains specs with expectations
A suite can have specs that fail
A suite can have incomplete specs

Failures:

"A suite can have specs that fail" FAILED
/Users/danielstjules/Desktop/exampleSpec.php:9
Expected false not to be false

Finished 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 or 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.

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

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);
    });
});
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].