{"id":13622726,"url":"https://github.com/danielstjules/pho","last_synced_at":"2025-04-04T21:07:28.276Z","repository":{"id":11126411,"uuid":"13487578","full_name":"danielstjules/pho","owner":"danielstjules","description":"BDD test framework for PHP","archived":false,"fork":false,"pushed_at":"2017-04-24T04:47:38.000Z","size":261,"stargazers_count":284,"open_issues_count":0,"forks_count":14,"subscribers_count":19,"default_branch":"master","last_synced_at":"2024-10-11T22:54:07.740Z","etag":null,"topics":["bdd","php","testing","testing-tools"],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/danielstjules.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-10-11T01:55:28.000Z","updated_at":"2024-07-22T08:12:15.000Z","dependencies_parsed_at":"2022-08-02T14:16:33.042Z","dependency_job_id":null,"html_url":"https://github.com/danielstjules/pho","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielstjules%2Fpho","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielstjules%2Fpho/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielstjules%2Fpho/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielstjules%2Fpho/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/danielstjules","download_url":"https://codeload.github.com/danielstjules/pho/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247249524,"owners_count":20908212,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bdd","php","testing","testing-tools"],"created_at":"2024-08-01T21:01:23.374Z","updated_at":"2025-04-04T21:07:28.259Z","avatar_url":"https://github.com/danielstjules.png","language":"PHP","funding_links":[],"categories":["测试","测试 Testing","PHP","Table of Contents","目录","Testing","测试( Testing )"],"sub_categories":["Testing","测试 Testing"],"readme":"![pho](http://danielstjules.com/github/pho-logo.png)\n\nBDD test framework for PHP, inspired by Jasmine and RSpec. Features a familiar\nsyntax, and a watch command to automatically re-run specs during development.\nIt can also be extended with custom matchers and reporters.\n\n[![Build Status](https://travis-ci.org/danielstjules/pho.svg?branch=master)](https://travis-ci.org/danielstjules/pho)\n\n * [Installation](#installation)\n * [Usage](#usage)\n * [Writing Specs](#writing-specs)\n * [Running Specs](#running-specs)\n * [Expectations/Matchers](#expectationsmatchers)\n * [Custom Matchers](#custom-matchers)\n * [Reporters](#reporters)\n * [Mocking](#mocking)\n * [Namespace](#namespace)\n\n## Installation\n\nThe following instructions outline installation using Composer. If you don't\nhave Composer, you can download it from [http://getcomposer.org/](http://getcomposer.org/)\nIf you're new to composer, make sure to add the vendor bin to your PATH:\n\n``` bash\n# Append the following to your profile file, for example in ~/.profile\nexport PATH=$HOME/.composer/vendor/bin:$PATH\n```\n\nTo install pho, run:\n\n``` bash\ncomposer global require danielstjules/pho\n```\n\n## Usage\n\n``` bash\nUsage: pho [options] [files]\n\nOptions\n\n   -a   --ascii                     Show ASCII art on completion\n   -b   --bootstrap   \u003cbootstrap\u003e   Bootstrap file to load\n   -f   --filter      \u003cpattern\u003e     Run specs containing a pattern\n   -h   --help                      Output usage information\n   -n   --namespace                 Only use namespaced functions\n   -r   --reporter    \u003cname\u003e        Specify the reporter to use\n   -s   --stop                      Stop on failure\n   -v   --version                   Display version number\n   -w   --watch                     Watch files for changes and rerun specs\n   -C   --no-color                  Disable terminal colors\n```\n\n## Writing Specs\n\nPho exposes a DSL for organizing and writing your tests, which includes the\nfollowing functions: `describe`, `context`, `it`, `before`, `after`, `beforeEach`,\n`afterEach` and `expect`. Equivalent functions for disabling specs and suites\nalso exist via `xdescribe`, `xcontext` and `xit`.\n\nTo create a suite, `describe` and `context` can be used by passing them a\nstring and function. Both are interchangeable, though context is more often\nnested in a describe to group some set of behaviour. `it` is then used to create\na spec, or test.\n\nA spec may contain multiple expectations or assertions, and will pass so long\nas all assertions pass and no exception is uncaught. For asserting values in pho,\n`expect` can be used. The function accepts the value to be tested, and may be\nchained with a handful of matchers.\n\n``` php\n\u003c?php\n\ndescribe('A suite', function() {\n    it('contains specs with expectations', function() {\n        expect(true)-\u003etoBe(true);\n    });\n\n    it('can have specs that fail', function() {\n        expect(false)-\u003enot()-\u003etoBe(false);\n    });\n\n    it('can have incomplete specs');\n});\n```\n\n![intro-screenshot](http://danielstjules.com/github/pho-intro.png)\n\nObjects may be passed between suites and specs with php's `use` keyword. Here's\nan example:\n\n``` php\ndescribe('Example', function() {\n    $object = new stdClass();\n    $object-\u003ename = 'pho';\n\n    context('name', function() use ($object) {\n        it('is set to pho', function()  use ($object) {\n            expect($object-\u003ename)-\u003etoBe('pho');\n        });\n    });\n});\n```\n\nThings can get a bit verbose when dealing with multiple objects that need to be\npassed into closures with `use`. To avoid such long lists of arguments, `$this`\ncan be used to set and retrieve values between suites and specs.\n\n``` php\ndescribe('SomeClass', function() {\n    $this-\u003ekey1 = 'initialValue';\n    $this-\u003ekey2 = 'initialValue';\n\n    context('methodOne()', function() {\n        $this-\u003ekey1 = 'changedValue';\n\n        it('contains a spec', function() {\n            expect($this-\u003ekey1)-\u003etoBe('changedValue');\n            expect($this-\u003ekey2)-\u003etoBe('initialValue');\n        });\n    });\n\n    context('methodTwo()', function() {\n        it('contains another spec', function() {\n            expect($this-\u003ekey1)-\u003etoBe('initialValue');\n            expect($this-\u003ekey2)-\u003etoBe('initialValue');\n        });\n    });\n});\n```\n\nHooks are available for running functions as setups and teardowns. `before` is\nran prior to any specs in a suite, and `after`, once all in the suite have been\nran. `beforeEach` and `afterEach` both run their closures once per spec. Note\nthat `beforeEach` and `afterEach` are both stackable, and will apply to specs\nwithin nested suites. Furthermore, Global hooks may be defined in your bootstrap\nfile. For example, an afterEach hook in a bootstrap file will run after every\ntest in your suite.\n\n``` php\ndescribe('Suite with Hooks', function() {\n    $this-\u003ecount = 0;\n\n    beforeEach(function() {\n        $this-\u003ecount = $this-\u003ecount + 1;\n    });\n\n    it('has a count equal to 1', function() {\n        expect($this-\u003ecount)-\u003etoEqual(1);\n        // A single beforeEach ran\n    });\n\n    context('nested suite', function() {\n        beforeEach(function() {\n            $this-\u003ecount = $this-\u003ecount + 1;\n        });\n\n        it('has a count equal to 3', function() {\n            expect($this-\u003ecount)-\u003etoEqual(3);\n            // Both beforeEach closures incremented the value\n        });\n    });\n});\n```\n\n## Running Specs\n\nBy default, pho looks for specs in either a `test` or `spec` folder under the\nworking directory. It will recurse through all subfolders and run any files\nending with `Spec.php`, ie: userSpec.php. Furthermore, continuous testing is as\neasy as using the `--watch` option, which will monitor all files in the path for\nchanges, and rerun specs on save.\n\n![watch](http://danielstjules.com/github/pho-watch.gif)\n\n## Expectations/Matchers\n\n#### Type Matching\n\n``` php\nexpect('pho')-\u003etoBeA('string');\nexpect(1)-\u003enotToBeA('string');\nexpect(1)-\u003enot()-\u003etoBeA('string');\n\nexpect(1)-\u003etoBeAn('integer'); // Alias for toBeA\nexpect('pho')-\u003enotToBeAn('integer');\nexpect('pho')-\u003enot()-\u003etoBeA('integer');\n```\n\n#### Instance Matching\n\n``` php\nexpect(new User())-\u003etoBeAnInstanceOf('User');\nexpect(new User())-\u003enot()-\u003etoBeAnInstanceOf('Post');\nexpect(new User())-\u003enotToBeAnInstanceOf('Post');\n```\n\n#### Strict Equality Matching\n\n``` php\nexpect(true)-\u003etoBe(true);\nexpect(true)-\u003enot()-\u003etoBe(false);\nexpect(true)-\u003enotToBe(false);\n\nexpect(['foo'])-\u003etoEqual(['foo']); // Alias for toBe\nexpect(['foo'])-\u003enot()-\u003etoEqual(true);\nexpect(['foo'])-\u003enotToEqual(true);\n```\n\n#### Loose Equality Matching\n\n``` php\nexpect(1)-\u003etoEql(true);\nexpect(new User('Bob'))-\u003enot()-\u003eToEql(new User('Alice'))\nexpect(new User('Bob'))-\u003enotToEql(new User('Alice'))\n```\n\n#### Length Matching\n\n``` php\nexpect(['tdd', 'bdd'])-\u003etoHaveLength(2);\nexpect('pho')-\u003enot()-\u003etoHaveLength(2);\nexpect('pho')-\u003enotToHaveLength(2);\n\nexpect([])-\u003etoBeEmpty();\nexpect('pho')-\u003enot()-\u003etoBeEmpty();\nexpect('pho')-\u003enotToBeEmpty();\n```\n\n#### Inclusion Matching\n\n``` php\nexpect('Spectacular!')-\u003etoContain('Spec');\nexpect(['a', 'b'])-\u003enot()-\u003etoContain('c');\nexpect(['a', 'b'])-\u003enotToContain('c');\n\nexpect('testing')-\u003etoContain('test', 'ing'); // Accepts multiple args\nexpect(['tdd', 'test'])-\u003enot()-\u003etoContain('bdd', 'spec');\nexpect(['tdd', 'test'])-\u003enotToContain('bdd', 'spec');\n\nexpect(['name' =\u003e 'pho'])-\u003etoHaveKey('name');\nexpect(['name' =\u003e 'pho'])-\u003enot()-\u003etoHaveKey('id');\nexpect(['name' =\u003e 'pho'])-\u003enotToHaveKey('id');\n```\n\n#### Pattern Matching\n\n``` php\nexpect('tdd')-\u003etoMatch('/\\w[D]{2}/i');\nexpect('pho')-\u003enot()-\u003etoMatch('/\\d+/');\nexpect('pho')-\u003enotToMatch('/\\d+/');\n\nexpect('username')-\u003etoStartWith('user');\nexpect('spec')-\u003enot()-\u003etoStartWith('test');\nexpect('spec')-\u003enotToStartWith('test');\n\nexpect('username')-\u003etoEndWith('name');\nexpect('spec')-\u003enot()-\u003etoEndWith('s');\nexpect('spec')-\u003enotToEndtWith('s');\n```\n\n#### Numeric Matching\n\n``` php\nexpect(2)-\u003etoBeGreaterThan(1);\nexpect(2)-\u003enot()-\u003etoBeGreaterThan(2);\nexpect(1)-\u003enotToBeGreaterThan(2);\n\nexpect(2)-\u003etoBeAbove(1); // Alias for toBeGreaterThan\nexpect(2)-\u003enot()-\u003etoBeAbove(2);\nexpect(1)-\u003enotToBeAbove(2);\n\nexpect(1)-\u003etoBeLessThan(2);\nexpect(1)-\u003enot()-\u003etoBeLessThan(1);\nexpect(2)-\u003enotToBeLessThan(1);\n\nexpect(1)-\u003etoBeBelow(2); // Alias for toBeLessThan\nexpect(1)-\u003enot()-\u003etoBeBelow(1);\nexpect(2)-\u003enotToBeBelow(1);\n\nexpect(1)-\u003etoBeWithin(1, 10); // Inclusive\nexpect(-2)-\u003enot()-\u003etoBeWithin(-1, 0);\nexpect(-2)-\u003enotToBeWithin(-1, 0);\n```\n\n#### Print Matching\n\n``` php\n$callable = function() {\n  echo 'test'\n};\n\nexpect($callable)-\u003etoPrint('test');\nexpect($callable)-\u003enot()-\u003etoPrint('testing');\nexpect($callable)-\u003enotToPrint('testing');\n```\n\n#### Exception Matching\n\n``` php\n$callable = function() {\n  throw new Custom\\Exception('error!');\n};\n\nexpect($callable)-\u003etoThrow('Custom\\Exception');\nexpect($callable)-\u003enot()-\u003etoThrow('\\ErrorException');\nexpect($callable)-\u003enotToThrow('\\ErrorException');\n```\n\n## Custom Matchers\n\nCustom matchers can be added by creating a class that implements\n`pho\\Expectation\\Matcher\\MatcherInterface` and registering the matcher with\n`pho\\Expectation\\Expectation::addMatcher()`. Below is an example of a basic\nmatcher:\n\n``` php\nnamespace example;\n\nuse pho\\Expectation\\Matcher\\MatcherInterface;\n\nclass ExampleMatcher implements MatcherInterface\n{\n    protected $expectedValue;\n\n    public function __construct($expectedValue)\n    {\n        $this-\u003eexpectedValue = $expectedValue;\n    }\n\n    public function match($actualValue)\n    {\n        return ($actualValue === $this-\u003eexpectedValue);\n    }\n\n    public function getFailureMessage($negated = false)\n    {\n        if (!$negated) {\n            return \"Expected value to be {$this-\u003eexpectedValue}\";\n        } else {\n            return \"Expected value not to be {$this-\u003eexpectedValue}\";\n        }\n    }\n}\n```\n\nRegistering it:\n\n``` php\nuse pho\\Expectation\\Expectation;\n\n// Register the matcher\nExpectation::addMatcher('toHaveValue', '\\example\\ExampleMatcher');\n```\n\nAnd that's it! You would now have access to the following:\n\n``` php\nexpect($actual)-\u003etoHaveValue($expected);\nexpect($actual)-\u003enot()-\u003etoHaveValue($expected);\nexpect($actual)-\u003enotToHaveValue($expected);\n```\n\n## Reporters\n\n#### dot (default)\n\n```\n$ pho --reporter dot exampleSpec.php\n\n.FI\n\nFailures:\n\n\"A suite can have specs that fail\" FAILED\n/Users/danielstjules/Desktop/exampleSpec.php:9\nExpected false not to be false\n\nFinished in 0.00125 seconds\n\n3 specs, 1 failure, 1 incomplete\n```\n\n#### spec\n\n```\n$ pho --reporter spec exampleSpec.php\n\nA suite\n    contains specs with expectations\n    can have specs that fail\n    can have incomplete specs\n\nFailures:\n\n\"A suite can have specs that fail\" FAILED\n/Users/danielstjules/Desktop/exampleSpec.php:9\nExpected false not to be false\n\nFinished in 0.0012 seconds\n\n3 specs, 1 failure, 1 incomplete\n```\n\n#### list\n\n```\n$ pho --reporter list exampleSpec.php\n\nA suite contains specs with expectations\nA suite can have specs that fail\nA suite can have incomplete specs\n\nFailures:\n\n\"A suite can have specs that fail\" FAILED\n/Users/danielstjules/Desktop/exampleSpec.php:9\nExpected false not to be false\n\nFinished in 0.0012 seconds\n\n3 specs, 1 failure, 1 incomplete\n```\n\n## Mocking\n\nPho doesn't currently provide mocks/stubs out of the box. Instead, it's suggested\nthat a mocking framework such as [prophecy](https://github.com/phpspec/prophecy)\nor [mockery](https://github.com/padraic/mockery) be used.\n\n*Note*: Tests can be failed from a test hook. If you need to check mock object\nexpectations after running a spec, you can do so from an afterEach hook.\n\n```php\ndescribe('A suite', function() {\n    afterEach(function() {\n        Mockery::close();\n    });\n\n    it('should check mock object expectations', function() {\n        $mock = Mockery::mock('simplemock');\n        $mock-\u003eshouldReceive('foo')-\u003ewith(5)-\u003eonce()-\u003eandReturn(10);\n        expect($mock-\u003efoo(5))-\u003etoBe(10);\n    });\n});\n```\n\n## Namespace\n\nIf you'd rather not have pho use the global namespace for its functions, you\ncan set the `--namespace` flag to force it to only use the pho namespace. This\nwill be a nicer alternative in PHP 5.6 with\n[https://wiki.php.net/rfc/use_function](https://wiki.php.net/rfc/use_function)\n\n``` php\npho\\describe('A suite', function() {\n    pho\\it('contains specs with expectations', function() {\n        pho\\expect(true)-\u003etoBe(true);\n    });\n\n    pho\\it('can have specs that fail', function() {\n        pho\\expect(false)-\u003enot()-\u003etoBe(false);\n    });\n});\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielstjules%2Fpho","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdanielstjules%2Fpho","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielstjules%2Fpho/lists"}