├── .gitignore ├── .travis.yml ├── LICENCE ├── README.md ├── behat.yml ├── composer.json ├── features ├── bootstrap │ ├── Console │ │ └── ApplicationTester.php │ └── PHPSpecContext.php └── use_data_providers_in_examples.feature ├── spec └── Coduo │ └── PhpSpec │ └── DataProvider │ └── Annotation │ └── ParserSpec.php └── src └── Coduo └── PhpSpec └── DataProvider ├── Annotation └── Parser.php ├── DataProviderExtension.php ├── Listener └── DataProviderListener.php └── Runner └── Maintainer └── DataProviderMaintainer.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | bin/ 3 | composer.lock 4 | 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.4 5 | - 5.5 6 | - 5.6 7 | 8 | env: 9 | - COMPOSER_OPTIONS='install --prefer-source' 10 | 11 | matrix: 12 | include: 13 | - php: 5.4 14 | env: COMPOSER_OPTIONS='update --prefer-lowest --prefer-source' 15 | 16 | before_install: 17 | - composer self-update 18 | 19 | before_script: 20 | - COMPOSER_ROOT_VERSION=dev-master composer $COMPOSER_OPTIONS 21 | 22 | script: 23 | - ./bin/phpspec run --format=dot 24 | - ./bin/behat -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Michal Dabrowski, Norbert Orzechowicz 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #PhpSpec data provider extension 2 | 3 | [![Build Status](https://travis-ci.org/coduo/phpspec-data-provider-extension.svg?branch=master)](https://travis-ci.org/coduo/phpspec-data-provider-extension) 4 | 5 | This extension allows you to create data providers for examples in specs. 6 | 7 | ## Installation 8 | 9 | ```shell 10 | composer require coduo/phpspec-data-provider-extension 11 | ``` 12 | 13 | ## Usage 14 | 15 | Enable extension in phpspec.yml file 16 | 17 | ``` 18 | extensions: 19 | - Coduo\PhpSpec\DataProvider\DataProviderExtension 20 | ``` 21 | 22 | Write a spec: 23 | 24 | ```php 25 | beConstructedWith($inputValue); 39 | $this->__toString()->shouldReturn($expectedValue); 40 | } 41 | 42 | public function positiveConversionExamples() 43 | { 44 | return array( 45 | array(1, '1'), 46 | array(1.1, '1.1'), 47 | array(new \DateTime, '\DateTime'), 48 | array(array('foo', 'bar'), 'Array(2)') 49 | ); 50 | } 51 | } 52 | ``` 53 | 54 | Write class for spec: 55 | 56 | ```php 57 | value = $value; 68 | } 69 | 70 | public function __toString() 71 | { 72 | $type = gettype($this->value); 73 | switch ($type) { 74 | case 'array': 75 | return sprintf('Array(%d)', count($this->value)); 76 | case 'object': 77 | return sprintf("\\%s", get_class($this->value)); 78 | default: 79 | return (string) $this->value; 80 | } 81 | } 82 | } 83 | ``` 84 | 85 | Run php spec 86 | 87 | ``` 88 | $ console bin/phpspec run -f pretty 89 | ``` 90 | 91 | You should get following output: 92 | 93 | ``` 94 | Coduo\ToString\String 95 | 96 | 12 ✔ convert input value into string 97 | 12 ✔ 1) it convert input value into string 98 | 12 ✔ 2) it convert input value into string 99 | 12 ✔ 3) it convert input value into string 100 | 12 ✔ 4) it convert input value into string 101 | 102 | 103 | 1 specs 104 | 5 examples (5 passed) 105 | 13ms 106 | ``` 107 | -------------------------------------------------------------------------------- /behat.yml: -------------------------------------------------------------------------------- 1 | default: 2 | suites: 3 | default: 4 | contexts: 5 | - PHPSpecContext -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "coduo/phpspec-data-provider-extension", 3 | "type": "library", 4 | "keywords": ["phpspec", "dataprovider", "extension", "coduo", "data", "provider"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Norbert Orzechowicz", 9 | "email": "norbert@orzechowicz.pl" 10 | }, 11 | { 12 | "name": "Michał Dąbrowski", 13 | "email": "dabrowski@brillante.pl" 14 | } 15 | ], 16 | "require": { 17 | "php": ">=5.4.0", 18 | "phpspec/phpspec": "^2" 19 | }, 20 | "require-dev": { 21 | "behat/behat": "3.0.*", 22 | "symfony/filesystem": "~2.3", 23 | "symfony/process": "~2.3", 24 | "symfony/console": "~2.3.2", 25 | "bossa/phpspec2-expect": "^1" 26 | }, 27 | "autoload": { 28 | "psr-0": { "Coduo\\PhpSpec": "src/"} 29 | }, 30 | "config": { 31 | "bin-dir": "bin" 32 | }, 33 | "extra": { 34 | "branch-alias": { 35 | "dev-master": "1.1-dev" 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /features/bootstrap/Console/ApplicationTester.php: -------------------------------------------------------------------------------- 1 | application = $application; 45 | } 46 | 47 | /** 48 | * @param string $input 49 | * 50 | * @return integer 51 | */ 52 | public function run($input) 53 | { 54 | $this->input = new StringInput($input); 55 | 56 | $this->output = new StreamOutput(fopen('php://memory', 'w', false)); 57 | 58 | $inputStream = $this->getInputStream(); 59 | rewind($inputStream); 60 | $this->application->getHelperSet() 61 | ->get('dialog') 62 | ->setInputStream($inputStream); 63 | 64 | $this->result = $this->application->run($this->input, $this->output); 65 | } 66 | 67 | /** 68 | * @param boolean 69 | * 70 | * @return string 71 | */ 72 | public function getDisplay($normalize = false) 73 | { 74 | rewind($this->output->getStream()); 75 | 76 | $display = stream_get_contents($this->output->getStream()); 77 | 78 | if ($normalize) { 79 | $display = str_replace(PHP_EOL, "\n", $display); 80 | } 81 | 82 | return $display; 83 | } 84 | 85 | /** 86 | * @return InputInterface 87 | */ 88 | public function getInput() 89 | { 90 | return $this->input; 91 | } 92 | 93 | /** 94 | * @return OutputInterface 95 | */ 96 | public function getOutput() 97 | { 98 | return $this->output; 99 | } 100 | 101 | /** 102 | * @param string $input 103 | */ 104 | public function putToInputStream($input) 105 | { 106 | fputs($this->getInputStream(), $input); 107 | } 108 | 109 | /** 110 | * @return resource 111 | */ 112 | private function getInputStream() 113 | { 114 | if (null === $this->inputStream) { 115 | $this->inputStream = fopen('php://memory', 'r+', false); 116 | } 117 | 118 | return $this->inputStream; 119 | } 120 | 121 | /** 122 | * @return int 123 | */ 124 | public function getResult() 125 | { 126 | return $this->result; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /features/bootstrap/PHPSpecContext.php: -------------------------------------------------------------------------------- 1 | workDir = sprintf( 27 | '%s/%s/', 28 | sys_get_temp_dir(), 29 | uniqid('PHPSpecDataProviderExtension') 30 | ); 31 | $fs = new Filesystem(); 32 | $fs->mkdir($this->workDir, 0777); 33 | chdir($this->workDir); 34 | } 35 | 36 | /** 37 | * @AfterScenario 38 | */ 39 | public function removeWorkDir() 40 | { 41 | $fs = new Filesystem(); 42 | $fs->remove($this->workDir); 43 | } 44 | 45 | /** 46 | * @Given /^the PhpSpecDataProviderExtension is enabled$/ 47 | */ 48 | public function thePhpspecdataproviderextensionIsEnabled() 49 | { 50 | $phpspecyml = <<workDir.'phpspec.yml', $phpspecyml); 56 | } 57 | 58 | /** 59 | * @When /^I write a (?:spec|class) "([^"]*)" with following code$/ 60 | */ 61 | public function iWriteASpecWithFollowingCode($file, PyStringNode $codeContent) 62 | { 63 | $dirname = dirname($file); 64 | if (!file_exists($dirname)) { 65 | mkdir($dirname, 0777, true); 66 | } 67 | 68 | file_put_contents($file, $codeContent->getRaw()); 69 | 70 | require_once($file); 71 | } 72 | 73 | /** 74 | * @Given /^I run phpspec$/ 75 | */ 76 | public function iRunPhpspec() 77 | { 78 | $application = new Application('2.0-dev'); 79 | $application->setAutoExit(false); 80 | 81 | $this->applicationTester = new Console\ApplicationTester($application); 82 | $this->applicationTester->run('run --no-interaction -f pretty'); 83 | } 84 | 85 | /** 86 | * @Then /^it should pass$/ 87 | */ 88 | public function itShouldPass() 89 | { 90 | expect($this->applicationTester->getResult())->toBe(0); 91 | } 92 | 93 | /** 94 | * @Given /^I should see "([^"]*)"$/ 95 | */ 96 | public function iShouldSee($message) 97 | { 98 | expect($this->applicationTester->getDisplay())->toMatch('/'.preg_quote($message, '/').'/sm'); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /features/use_data_providers_in_examples.feature: -------------------------------------------------------------------------------- 1 | Feature: Use data providers in examples 2 | In order to run example multiple times against different data 3 | I need to enable PHPSpecDataProviderExtension in phpspec.yml file 4 | 5 | Scenario: Positive match with Coduo matcher 6 | Given the PhpSpecDataProviderExtension is enabled 7 | When I write a spec "spec/Coduo/ToString/StringSpec.php" with following code 8 | """ 9 | beConstructedWith($inputValue); 23 | $this->__toString()->shouldReturn($expectedValue); 24 | } 25 | 26 | public function positiveConversionExamples() 27 | { 28 | return array( 29 | array(1, '1'), 30 | array(1.1, '1.1'), 31 | array(new \DateTime, '\DateTime'), 32 | array(array('foo', 'bar'), 'Array(2)') 33 | ); 34 | } 35 | } 36 | """ 37 | And I write a class "src/Coduo/ToString/String.php" with following code 38 | """ 39 | value = $value; 50 | } 51 | 52 | public function __toString() 53 | { 54 | $type = gettype($this->value); 55 | switch ($type) { 56 | case 'array': 57 | return sprintf('Array(%d)', count($this->value)); 58 | case 'object': 59 | return sprintf("\\%s", get_class($this->value)); 60 | default: 61 | return (string) $this->value; 62 | } 63 | } 64 | } 65 | """ 66 | And I run phpspec 67 | Then it should pass 68 | And I should see "✔ convert input value into string" 69 | And I should see "✔ 1) it convert input value into string" 70 | And I should see "✔ 2) it convert input value into string" 71 | And I should see "✔ 3) it convert input value into string" 72 | And I should see "✔ 4) it convert input value into string" 73 | 74 | Scenario: Positive match with Coduo matcher with trailing phpspec's test double arguments 75 | Given the PhpSpecDataProviderExtension is enabled 76 | When I write a spec "spec/Coduo/Date/DateRangeSpec.php" with following code 77 | """ 78 | beConstructedWith($inputValue, $date); 94 | 95 | $date->format('Ymd')->willReturn('2035-10-28'); 96 | 97 | $this->getFormattedRange()->shouldBeLike('1985-10-26 - 2035-10-28'); 98 | } 99 | 100 | public function positiveConversionExamples() 101 | { 102 | return array( 103 | array(new \DateTime('1985-10-26')), 104 | ); 105 | } 106 | } 107 | """ 108 | And I write a class "src/Coduo/Date/DateRange.php" with following code 109 | """ 110 | start = $start; 122 | $this->end = $end; 123 | } 124 | 125 | public function getFormattedRange() 126 | { 127 | return $this->start->format('Y-m-d') . ' - ' . $this->end->format('Ymd'); 128 | } 129 | } 130 | """ 131 | And I run phpspec 132 | Then it should pass 133 | And I should see "✔ returns a formatted date range" 134 | And I should see "✔ 1) it returns a formatted date range" 135 | 136 | -------------------------------------------------------------------------------- /spec/Coduo/PhpSpec/DataProvider/Annotation/ParserSpec.php: -------------------------------------------------------------------------------- 1 | getDocComment()->willReturn(<<getDataProvider($reflectionMethod)->shouldReturn('positiveExample'); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Coduo/PhpSpec/DataProvider/Annotation/Parser.php: -------------------------------------------------------------------------------- 1 | getDocComment())) { 16 | return null; 17 | } 18 | 19 | if (0 === preg_match(self::DATA_PROVIDER_PATTERN, $docComment, $matches)) { 20 | return null; 21 | } 22 | 23 | return $matches[1]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Coduo/PhpSpec/DataProvider/DataProviderExtension.php: -------------------------------------------------------------------------------- 1 | setShared('event_dispatcher.listeners.data_provider', function ($c) { 18 | return new DataProviderListener(); 19 | }); 20 | 21 | $container->set('runner.maintainers.data_provider', function ($c) { 22 | return new DataProviderMaintainer(); 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Coduo/PhpSpec/DataProvider/Listener/DataProviderListener.php: -------------------------------------------------------------------------------- 1 | array('beforeSpecification'), 16 | ); 17 | } 18 | 19 | public function beforeSpecification(SpecificationEvent $event) 20 | { 21 | $examplesToAdd = array(); 22 | $parser = new Parser(); 23 | foreach ($event->getSpecification()->getExamples() as $example) { 24 | $dataProviderMethod = $parser->getDataProvider($example->getFunctionReflection()); 25 | 26 | if (null !== $dataProviderMethod) { 27 | 28 | if (!$example->getSpecification()->getClassReflection()->hasMethod($dataProviderMethod)) { 29 | return false; 30 | } 31 | 32 | $subject = $example->getSpecification()->getClassReflection()->newInstance(); 33 | $providedData = $example->getSpecification()->getClassReflection()->getMethod($dataProviderMethod)->invoke($subject); 34 | 35 | if (is_array($providedData)) { 36 | foreach ($providedData as $i => $dataRow) { 37 | $examplesToAdd[] = new ExampleNode($i+1 . ') ' . $example->getTitle(), $example->getFunctionReflection()); 38 | } 39 | } 40 | 41 | } 42 | } 43 | foreach ($examplesToAdd as $example) { 44 | $event->getSpecification()->addExample($example); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Coduo/PhpSpec/DataProvider/Runner/Maintainer/DataProviderMaintainer.php: -------------------------------------------------------------------------------- 1 | haveValidDataProvider($example); 24 | } 25 | 26 | /** 27 | * @param ExampleNode $example 28 | * @param SpecificationInterface $context 29 | * @param MatcherManager $matchers 30 | * @param CollaboratorManager $collaborators 31 | */ 32 | public function prepare(ExampleNode $example, SpecificationInterface $context, 33 | MatcherManager $matchers, CollaboratorManager $collaborators) 34 | { 35 | $exampleNum = $this->getExampleNumber($example->getTitle()); 36 | $providedData = $this->getDataFromProvider($example); 37 | 38 | if (! array_key_exists($exampleNum, $providedData)) { 39 | return ; 40 | } 41 | 42 | $data = $providedData[$exampleNum]; 43 | 44 | foreach ($example->getFunctionReflection()->getParameters() as $position => $parameter) { 45 | if (!isset($data[$position])) { 46 | continue; 47 | } 48 | $collaborators->set($parameter->getName(), $data[$position]); 49 | } 50 | } 51 | 52 | /** 53 | * @param ExampleNode $example 54 | * @param SpecificationInterface $context 55 | * @param MatcherManager $matchers 56 | * @param CollaboratorManager $collaborators 57 | */ 58 | public function teardown(ExampleNode $example, SpecificationInterface $context, 59 | MatcherManager $matchers, CollaboratorManager $collaborators) 60 | { 61 | } 62 | 63 | /** 64 | * @return int 65 | */ 66 | public function getPriority() 67 | { 68 | return 50; 69 | } 70 | 71 | private function haveValidDataProvider(ExampleNode $example) 72 | { 73 | $parser = new Parser(); 74 | $dataProviderMethod = $parser->getDataProvider($example->getFunctionReflection()); 75 | 76 | if (!isset($dataProviderMethod)) { 77 | return false; 78 | } 79 | 80 | if (!$example->getSpecification()->getClassReflection()->hasMethod($dataProviderMethod)) { 81 | return false; 82 | } 83 | 84 | $subject = $example->getSpecification()->getClassReflection()->newInstance(); 85 | $providedData = $example->getSpecification()->getClassReflection()->getMethod($dataProviderMethod)->invoke($subject); 86 | 87 | if (!is_array($providedData)) { 88 | return false; 89 | } 90 | 91 | $exampleParamsCount = count($example->getFunctionReflection()->getParameters()); 92 | foreach ($providedData as $dataRow) { 93 | if (!is_array($dataRow)) { 94 | return false; 95 | } 96 | } 97 | 98 | return true; 99 | } 100 | 101 | /** 102 | * @param ExampleNode $example 103 | * @return bool|mixed 104 | */ 105 | private function getDataFromProvider(ExampleNode $example) 106 | { 107 | $parser = new Parser(); 108 | $dataProviderMethod = $parser->getDataProvider($example->getFunctionReflection()); 109 | 110 | if (!isset($dataProviderMethod)) { 111 | return array(); 112 | } 113 | 114 | if (!$example->getSpecification()->getClassReflection()->hasMethod($dataProviderMethod)) { 115 | return array(); 116 | } 117 | 118 | $subject = $example->getSpecification()->getClassReflection()->newInstance(); 119 | $providedData = $example->getSpecification()->getClassReflection()->getMethod($dataProviderMethod)->invoke($subject); 120 | 121 | return (is_array($providedData)) ? $providedData : array(); 122 | } 123 | 124 | /** 125 | * @param $title 126 | * @return int 127 | */ 128 | private function getExampleNumber($title) 129 | { 130 | if (0 === preg_match(self::EXAMPLE_NUMBER_PATTERN, $title, $matches)) { 131 | return 0; 132 | } 133 | 134 | return (int) $matches[1] - 1; 135 | } 136 | } 137 | --------------------------------------------------------------------------------