├── .github └── workflows │ └── ci.yml ├── .gitignore ├── README.md ├── bin └── update-tests ├── cache └── .gitignore ├── changelog.md ├── composer.json ├── docs ├── README.md └── _config.yml ├── phpspec.yml ├── phpunit.xml ├── src ├── functions.php ├── tad │ └── FunctionMocker │ │ ├── Call │ │ ├── CallHandlerInterface.php │ │ ├── Logger │ │ │ ├── CallLoggerFactory.php │ │ │ ├── LoggerInterface.php │ │ │ └── SpyCallLogger.php │ │ └── Verifier │ │ │ ├── AbstractVerifier.php │ │ │ ├── CallVerifierFactory.php │ │ │ ├── FunctionCallVerifier.php │ │ │ ├── InstanceMethodCallVerifier.php │ │ │ ├── StaticMethodCallVerifier.php │ │ │ └── VerifierInterface.php │ │ ├── CallTrace.php │ │ ├── Checker.php │ │ ├── Forge │ │ ├── InstanceMethodRequest.php │ │ ├── Step.php │ │ └── StepInterface.php │ │ ├── FunctionMocker.php │ │ ├── MatchingStrategy │ │ ├── AbstractMatchingStrategy.php │ │ ├── AtLeastMatchingStrategy.php │ │ ├── AtMostMatchingStrategy.php │ │ ├── EqualMatchingStrategy.php │ │ ├── GreaterThanMatchingStrategy.php │ │ ├── LessThanMatchingStrategy.php │ │ ├── MatchingStrategyFactory.php │ │ ├── MatchingStrategyInterface.php │ │ └── NotEqualMatchingStrategy.php │ │ ├── Method │ │ └── Verifier.php │ │ ├── MockWrapper.php │ │ ├── PHPUnitFrameworkAssertWrapper.php │ │ ├── ReplacementRequest.php │ │ ├── Replacers │ │ └── InstanceForger.php │ │ ├── ReturnValue.php │ │ ├── SpoofTestCase.php │ │ ├── Template │ │ ├── ClassTemplate.php │ │ ├── ClassTemplateInterface.php │ │ ├── Extender │ │ │ ├── AbstractExtender.php │ │ │ ├── ExtenderInterface.php │ │ │ └── SpyExtender.php │ │ ├── LoggingMethodCode.php │ │ ├── MethodCode.php │ │ ├── MethodCodeInterface.php │ │ └── VerifyingClassTemplate.php │ │ └── Utils.php └── utils.php └── tests ├── _bootstrap.php ├── classes.php ├── functions.php ├── tad ├── FunctionMocker │ ├── AbstractClassMockingTest.php │ ├── AliasedClassesTest.php │ ├── AssertionWrappingTest.php │ ├── BatchReplaceTest.php │ ├── Forge │ │ └── StepTest.php │ ├── ForgeTest.php │ ├── FunctionArgsConstraintsCheckTest.php │ ├── FunctionReplacementInOrderTest.php │ ├── FunctionReplacementTest.php │ ├── GlobalReplacementTest.php │ ├── InstanceMethodTest.php │ ├── InterfaceMockingTest.php │ ├── PassAndCallOriginalTest.php │ ├── PatchworkConfigurationTest.php │ ├── StaticMethodConstraintsCheckTest.php │ ├── StaticMethodTest.php │ ├── TestWrappingTest.php │ ├── TraitMockingTest.php │ └── UtilsTest.php └── ReplacementRequestTest.php └── test_supports ├── Class1.php ├── Class2.php ├── Class3.php └── TestCase.php /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | run: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | os: [ubuntu-latest, windows-latest] 11 | php-version: ['7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] 12 | name: PHP ${{ matrix.php-version }} on ${{ matrix.os }} 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | 18 | - name: Install PHP 19 | uses: shivammathur/setup-php@v2 20 | with: 21 | php-version: ${{ matrix.php-version }} 22 | 23 | - name: Install dependencies 24 | run: composer install 25 | 26 | - name: PHPUnit tests 27 | run: "vendor/bin/phpunit" 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | .idea 3 | 4 | ### Composer template 5 | composer.phar 6 | vendor/ 7 | 8 | # Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file 9 | # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file 10 | composer.lock 11 | 12 | .gitignore 13 | patchwork.json 14 | cache/* 15 | pw-cs-*.yml 16 | .phpunit.result.cache 17 | -------------------------------------------------------------------------------- /bin/update-tests: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This file updates tests according to the phpunit library used for current php version, or php version in 1st argument. 3 | # Usage: 4 | # update-tests - to update tests according to the phpunit library used for current php version. 5 | # update-tests x.x - to update tests according to the phpunit library used for specific php version x.x, where x.x = 5.6|7.0|7.1|7.2|7.3|7.4|8.0|8.1|8.2|8.3|8.4. 6 | 7 | # Directory with phpunit tests. 8 | TEST_DIR="tests" 9 | 10 | if grep -q Microsoft /proc/version; then 11 | DEV_MODE=$(cmd.exe /c echo %COMPOSER_DEV_MODE% | sed -nr 's/\r//p') 12 | else 13 | DEV_MODE=$COMPOSER_DEV_MODE 14 | fi 15 | 16 | if [[ $1 == '' && $DEV_MODE != '1' ]]; then 17 | # Script works with composer in dev mode only. 18 | exit 0 19 | fi 20 | 21 | if [[ $1 == '' ]]; then 22 | PHP_VERSION=$(php -v | sed -nr "s/PHP ([^.]*?\.[^.]*?)\..*/\1/p") 23 | else 24 | if [[ $1 == 'revert' ]]; then 25 | # Restore test files to the current branch version. 26 | git checkout -- $TEST_DIR 27 | echo "Tests reverted to the initial state." 28 | exit 0 29 | fi 30 | PHP_VERSION=$1 31 | fi 32 | 33 | echo "PHP_VERSION: $PHP_VERSION" 34 | 35 | VERSION_FILE="vendor/phpunit/phpunit/src/Runner/Version.php" 36 | CURRENT_PHP_UNIT='' 37 | 38 | RESULT=$(test -f $VERSION_FILE && sed -nr "s/.*new Version.+'(.+\..+)\..*'.*/\1/p" $VERSION_FILE) 39 | 40 | if [[ $? == 0 ]]; then 41 | CURRENT_PHP_UNIT=$RESULT 42 | echo "CURRENT_PHP_UNIT: $CURRENT_PHP_UNIT" 43 | else 44 | echo "CURRENT_PHP_UNIT: Not found." 45 | fi 46 | 47 | if [[ $PHP_VERSION == '5.6' ]]; then 48 | PHP_UNIT='5.7' 49 | elif [[ $PHP_VERSION == '7.0' ]]; then 50 | PHP_UNIT='6.5' 51 | elif [[ $PHP_VERSION == '7.1' ]]; then 52 | PHP_UNIT='7.5' 53 | elif [[ $PHP_VERSION == '7.2' ]]; then 54 | PHP_UNIT='8.5' 55 | elif [[ $PHP_VERSION == '7.3' || $PHP_VERSION == '7.4' || $PHP_VERSION == '8.0' || $PHP_VERSION == '8.1' || $PHP_VERSION == '8.2' || $PHP_VERSION == '8.3' || $PHP_VERSION == '8.4' ]]; then 56 | PHP_UNIT='9.5' 57 | fi 58 | 59 | if [[ $PHP_UNIT == '' ]]; then 60 | echo "Wrong PHP version: $PHP_VERSION" 61 | exit 1 62 | fi 63 | 64 | if [[ $1 == '' && $CURRENT_PHP_UNIT == "$PHP_UNIT" ]]; then 65 | # Do nothing if current version of phpunit is the same as required. Important on CI. 66 | # Anytime force update available specifying the first argument like 'update-phpunit 7.0' 67 | echo "Nothing to do with phpunit." 68 | exit 0 69 | fi 70 | 71 | # Restore test files to the current branch version. 72 | git checkout -- $TEST_DIR 73 | 74 | if [[ $PHP_UNIT == '5.7' || $PHP_UNIT == '6.5' || $PHP_UNIT == '7.5' ]]; then 75 | echo "Preparing tests for phpunit-$PHP_UNIT" 76 | find $TEST_DIR -type f -exec sed -i "s/: void//g" {} \; 77 | fi 78 | 79 | exit 0 80 | -------------------------------------------------------------------------------- /cache/.gitignore: -------------------------------------------------------------------------------- 1 | * -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). 3 | 4 | ## [Unreleased][unreleased] 5 | 6 | ## [2.0.0] 7 | ### Added 8 | 9 | - Support for PHP 8.4. (thanks @kagg-design) 10 | 11 | ### Changed 12 | - The minimum required PHP version is now 7.2. 13 | 14 | ## [1.4.0] 15 | ### Added 16 | - PHP 8 compatibility and test coverage (thanks @kagg-design) 17 | 18 | ### Fixed 19 | - Windows compatibility issues (thanks @kagg-design) 20 | 21 | ## [1.3.9] 22 | ### Added 23 | - the `FunctionMocker::replaceInOrder` method to return values from a set from a replaced function or static method, thanks @wppunk, fixes #11 24 | 25 | ## [1.3.8] 26 | ### Fixed 27 | - compatibility with PHPUnit 6.0+ 28 | - an issue where Patchwork cache would generate a fatal on Windows machines 29 | 30 | ## [1.3.7] 31 | ### Fixed 32 | - issue that would generate en exception when providing an empty configuration to the `FunctionMocker::init` method 33 | 34 | ## [1.3.6] 35 | ### Fixed 36 | - smaller PHPUnit `<= 6.0` incompatibilities 37 | 38 | ## [1.3.5] 39 | ### Fixed 40 | - Patchwork library configuration creation to address caching issues 41 | 42 | ## [1.3.4] 43 | ### Fixed 44 | - really fixed the issue with locating the `vendor` folder that would generate PHP notices (thanks @sciamannikoo) 45 | 46 | ## [1.3.3] 47 | ### Fixed 48 | - an issue with locating the `vendor` folder that would generate PHP notices 49 | 50 | ## [1.3.2] 51 | ### Changed 52 | - `phpunit/phpunit` required version to `>=5.7` 53 | 54 | ## [1.3.1] 55 | ### Added 56 | - support to replace non defined functions in tests 57 | - expose high level API as functions on the `tad\FunctionMocker` namespace 58 | 59 | ## [1.3.0] 60 | ### Added 61 | - support for p[PhpUnit](https://phpunit.de/ "PHPUnit The PHP Testing Framework") version `6.0` (issue #4) 62 | 63 | ## [1.2.2] 64 | ### Fixed 65 | - argument setting for closure return values 66 | 67 | ## [1.2.1] 68 | ### Added 69 | - require `phpunit/phpunit` version `5.4` 70 | 71 | ## [1.2.0] 72 | ### Added 73 | - fallback caching path to the `init` method to avoid no caching in place at all 74 | - support for internal function replacement (issue #3) 75 | 76 | ### Fixed 77 | - hard-coded `vendor` path (issue #2) 78 | 79 | ### Removed 80 | - support to replace non-defined functions 81 | 82 | ## [1.1.0] - 2016-06-03 83 | ### Added 84 | - this changelog 85 | 86 | ### Changed 87 | - updated `antecedent/patchwork` dependency to `1.5` 88 | - updated `phpunit/phpunit` dependency to `5.4` 89 | 90 | ### Fixed 91 | - issue where verifying the same instance replacement would result in double instance creations 92 | 93 | [unreleased]: https://github.com/lucatume/function-mocker/compare/1.4.0...HEAD 94 | [2.0.0]: https://github.com/lucatume/function-mocker/compare/1.4.0...2.0.0 95 | [1.4.0]: https://github.com/lucatume/function-mocker/compare/1.3.9...1.4.0 96 | [1.3.9]: https://github.com/lucatume/function-mocker/compare/1.3.8...1.3.9 97 | [1.3.8]: https://github.com/lucatume/function-mocker/compare/1.3.7...1.3.8 98 | [1.3.7]: https://github.com/lucatume/function-mocker/compare/1.3.6...1.3.7 99 | [1.3.6]: https://github.com/lucatume/function-mocker/compare/1.3.5...1.3.6 100 | [1.3.5]: https://github.com/lucatume/function-mocker/compare/1.3.4...1.3.5 101 | [1.3.4]: https://github.com/lucatume/function-mocker/compare/1.3.3...1.3.4 102 | [1.3.3]: https://github.com/lucatume/function-mocker/compare/1.3.2...1.3.3 103 | [1.3.2]: https://github.com/lucatume/function-mocker/compare/1.3.1...1.3.2 104 | [1.3.1]: https://github.com/lucatume/function-mocker/compare/1.3.0...1.3.1 105 | [1.3.0]: https://github.com/lucatume/function-mocker/compare/1.2.2...1.3.0 106 | [1.2.2]: https://github.com/lucatume/function-mocker/compare/1.2.1...1.2.2 107 | [1.2.1]: https://github.com/lucatume/function-mocker/compare/1.2.0...1.2.1 108 | [1.2.0]: https://github.com/lucatume/function-mocker/compare/1.1.0...1.2.0 109 | [1.1.0]: https://github.com/lucatume/function-mocker/compare/1.0.5...1.1.0 110 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lucatume/function-mocker", 3 | "description": "Function mocking with Patchwork", 4 | "license": "GPL-2.0+", 5 | "authors": [ 6 | { 7 | "name": "theAverageDev", 8 | "email": "luca@theaveragedev.com" 9 | } 10 | ], 11 | "minimum-stability": "stable", 12 | "require": { 13 | "php": ">=7.1", 14 | "phpunit/phpunit": "7.5 - 9.6", 15 | "antecedent/patchwork": "^2.2.0", 16 | "lucatume/args": "^1.0.1" 17 | }, 18 | "autoload": { 19 | "psr-0": { 20 | "tad\\FunctionMocker": "src" 21 | }, 22 | "files": [ 23 | "src/functions.php" 24 | ] 25 | }, 26 | "bin": [ 27 | "bin/update-tests" 28 | ], 29 | "scripts": { 30 | "pre-update-cmd": "update-tests", 31 | "update-tests": "update-tests", 32 | "revert-tests": "composer update-tests revert", 33 | "phpcs": "phpcs --colors --standard=phpcs.xml", 34 | "unit": "phpunit" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Function Mocker 2 | 3 | *A [Patchwork](http://antecedent.github.io/patchwork/) powered function mocker.* 4 | 5 | [![Build Status](https://travis-ci.org/lucatume/function-mocker.svg?branch=master)](https://travis-ci.org/lucatume/function-mocker) 6 | 7 | ## Show me the code 8 | This can be written in a [PHPUnit](http://phpunit.de/) test suite 9 | 10 | ```php 11 | log('error', 'There was an error'); 51 | 52 | $expected = sprintf('[%s] error - There was an error', date(DATE_ATOM, $mockTime)); 53 | $update_option->wasCalledWithOnce(['log', $expected]); 54 | } 55 | } 56 | ``` 57 | 58 | ## Installation 59 | Either zip and move it to the appropriate folder or use [Composer](https://getcomposer.org/) 60 | 61 | composer require lucatume/function-mocker:~1.0 62 | 63 | ## Usage 64 | In a perfect world you should never need to mock static methods and functions, should use [TDD](http://en.wikipedia.org/wiki/Test-driven_development) to write better object-oriented code and use it as a design tool. 65 | But sometimes a grim and sad need to mock those functions and static methods might arise and this library is here to help. 66 | 67 | ### Bootstrapping 68 | To make Fucntion Mocker behave in its wrapping power (a power granted by [patchwork](https://github.com/antecedent/patchwork)) the `FunctionMocker::init` method needs to be called in the proper bootstrap file of [Codeception](http://codeception.com/) or [PHPUnit](http://phpunit.de/) like this 69 | 70 | ```php 71 | dirname(__DIR__)]); 78 | ``` 79 | 80 | The `init` method will accept a configuration array supporting the following arguments: 81 | 82 | * `include` or `whitelist` - array|string; a list of **absolute** paths that should be included in the patching. 83 | * `exclude` or `blacklist` - array|string; a list of **absolute** paths that should be excluded in the patching. 84 | * `cache-path` - string; the **absolute** path to the folder where Pathchwork should cache the wrapped files. 85 | * `redefinable-internals` - array; a list of internal PHP functions that are available for replacement; if an internal function (a function defined by PHP) is not listed here then it will never be replaced in the tests. 86 | 87 | 88 | ```php 89 | \tad\FunctionMocker\FunctionMocker::init([ 90 | 'whitelist' => [dirname(__DIR__) . '/src',dirname(__DIR__) . '/vendor'], 91 | 'blacklist' => [dirname(__DIR__) . '/included', dirname(__FILE__) . 'patchwork-cache', dirname(__DIR__)], 92 | 'cache-path' => dirname(__DIR__) . 'patchwork-cache, 93 | 'redefinable-internals' => ['time', 'filter_var'] 94 | ]); 95 | ``` 96 | 97 | Excluding the project root folder, `dirname(__DIR__)` in the examples, is usually a good idea. 98 | 99 | **Note**: the library will ignore `patchwork.json` files even if no configuration is provided to the `FunctionMocker::init` method. 100 | 101 | #### Initialization parameters 102 | Function mocker will take care of initializing Patchwork with some sensible defaults but those initialization parameters can be customized: 103 | 104 | * `whitelist` - array or string, def. empty; a list of absolute paths that should be included in the patching. 105 | * `blacklist` - array or string, def. empty; a list of absolute paths that should be excluded from the patching; the Patchwork library itself and the Function Mocker library are always excluded. 106 | * `cache-path` - string, def. `cache` folder; the absolute path to the folder where Pathcwork should cache the wrapped files. 107 | * `redefinable-internals` array, def. empty; a list of internal PHP functions that are available for replacement; any *internal* function (defined by the PHP Standard Library) that needs to be replaced in the tests should be listed here. 108 | 109 | ### setUp and tearDown methods 110 | The library is meant to be used in the context of a [PHPUnit](http://phpunit.de/) test case and provides two `static` methods that **must** be inserted in the test case `setUp` and `tearDown` method for the function mocker to work properly: 111 | 112 | ```php 113 | class MyTest extends \PHPUnit_Framework_TestCase { 114 | public function setUp(){ 115 | // before any other set up method 116 | FunctionMocker::setUp(); 117 | ... 118 | } 119 | 120 | public function tearDown(){ 121 | ... 122 | 123 | // after any other tear down method 124 | FunctionMocker::tearDown(); 125 | } 126 | } 127 | ``` 128 | 129 | ### Functions 130 | 131 | #### Replacing functions 132 | The library will allow for replacement of **defined and undefined** functions at test run time using the `FunctionMocker::replace` method like: 133 | 134 | ```php 135 | FunctionMocker::replace('myFunction', $returnValue); 136 | ``` 137 | 138 | and will allow setting a return value as a real value or as a function callback: 139 | 140 | ```php 141 | public function testReplacedFunctionReturnsValue(){ 142 | FunctionMocker::replace('myFunction', 23); 143 | 144 | $this->assertEquals(23, myFunction()); 145 | } 146 | 147 | public fuction testReplacedFunctionReturnsCallback(){ 148 | FunctionMocker::replace('myFunction', function($arg){ 149 | return $arg + 1; 150 | }); 151 | 152 | $this->assertEquals(24, myFunction()); 153 | } 154 | ``` 155 | 156 | #### Spying on functions 157 | If the value returned by the `FunctionMocker::replace` method is stored in a variable than checks for calls can be made on that function: 158 | 159 | ```php 160 | public function testReplacedFunctionReturnsValue(){ 161 | $myFunction = FunctionMocker::replace('myFunction', 23); 162 | 163 | $this->assertEquals(23, myFunction()); 164 | 165 | $myFunction->wasCalledOnce(); 166 | $myFunction->wasCalledWithOnce(23); 167 | } 168 | ``` 169 | 170 | The methods available for a function spy are the ones listed below in the "Methods" section. 171 | 172 | #### Batch replacement of functions 173 | When in need to stub out a batch of functions needed for a component to work this can be done: 174 | 175 | ```php 176 | public function testBatchFunctionReplacement(){ 177 | $functions = ['functionOne', 'functionTwo', 'functionThree', ...]; 178 | 179 | FunctionMocker::replace($functions, function($arg){ 180 | return $arg; 181 | }); 182 | 183 | foreach ($functions as $f){ 184 | $this->assertEquals('foo', $f('foo')); 185 | } 186 | } 187 | ``` 188 | 189 | When replacing a batch of functions the return value will be an `array` of spy objects that can be referenced using the function name: 190 | 191 | ```php 192 | public function testBatchFunctionReplacement(){ 193 | $functions = ['functionOne', 'functionTwo', 'functionThree', ...]; 194 | 195 | $replacedFunctions = FunctionMocker::replace($functions, function($arg){ 196 | return $arg; 197 | }); 198 | 199 | functionOne(); 200 | 201 | $functionOne = $replacedFunctions['functionOne']; 202 | $functionOne->wasCalledOnce(); 203 | 204 | } 205 | ``` 206 | 207 | ### Static methods 208 | 209 | #### Replacing static methods 210 | Similarly to functions the library will allow for replacement of **defined** static methods using the `FunctionMocker::replace` method 211 | 212 | ```php 213 | public function testReplacedStaticMethodReturnsValue(){ 214 | FunctionMocker::replace('Post::getContent', 'Lorem ipsum'); 215 | 216 | $this->assertEquals('Lorem ipsum', Post::getContent()); 217 | } 218 | ``` 219 | 220 | again similarly to functions a callback function return value can be set: 221 | 222 | ```php 223 | public function testReplacedStaticMethodReturnsCallback(){ 224 | FunctionMocker::replace('Post::formatTitle', function($string){ 225 | return "foo $string baz"; 226 | }); 227 | 228 | $this->assertEquals('foo lorem baz', Post::formatTitle('lorem')); 229 | } 230 | ``` 231 | 232 | >Note that only `public static` methods can be replaced. 233 | 234 | #### Spying of static methods 235 | Storing the return value of the `FunctionMocker::replace` function allows spying on static methods using the methods listed in the "Methods" section below like: 236 | 237 | ```php 238 | public function testReplacedStaticMethodReturnsValue(){ 239 | $getContent = FunctionMocker::replace('Post::getContent', 'Lorem ipsum'); 240 | 241 | $this->assertEquals('Lorem ipsum', Post::getContent()); 242 | 243 | $getContent->wasCalledOnce(); 244 | $getContent->wasNotCalledWith('some'); 245 | ... 246 | } 247 | ``` 248 | 249 | #### Batch replacement of static methods 250 | Static methods too can be replaced in a batch assigning to any replaced method the same return value or callback: 251 | 252 | ```php 253 | public function testBatchReplaceStaticMethods(){ 254 | $methods = ['Foo::one', 'Foo::two', 'Foo::three']; 255 | 256 | FunctionMocker::replace($methods, 'foo'); 257 | 258 | $this->assertEquals('foo', Foo::one()); 259 | $this->assertEquals('foo', Foo::two()); 260 | $this->assertEquals('foo', Foo::three()); 261 | } 262 | ``` 263 | 264 | When batch replacing static methods `FunctionMocker::replace` will return an array of spy objects indexed by the method name that can be used as any other static method spy object; 265 | 266 | ```php 267 | public function testBatchReplaceStaticMethods(){ 268 | $methods = ['Foo::one', 'Foo::two', 'Foo::three']; 269 | 270 | $replacedMethods = FunctionMocker::replace($methods, 'foo'); 271 | 272 | Foo::one(); 273 | 274 | $one = $replacedMethods['one']; 275 | $one->wasCalledOnce(); 276 | } 277 | ``` 278 | 279 | ### Instance methods 280 | 281 | #### Replacing instance methods 282 | When trying to replace an instance method the `FunctionMocker::replace` method will return an extended PHPUnit mock object implementing all the [original methods](https://phpunit.de/manual/current/en/test-doubles.html) and some (see below) 283 | 284 | ```php 285 | // file SomeClass.php 286 | 287 | class SomeClass{ 288 | 289 | protected $dep; 290 | 291 | public function __construct(Dep $dep){ 292 | $this->dep = $dep; 293 | } 294 | 295 | public function someMethod(){ 296 | return $this->dep->go(); 297 | } 298 | } 299 | 300 | // file SomeClassTest.php 301 | 302 | use tad\FunctionMocker\FunctionMocker; 303 | 304 | class SomeClassTest extends PHPUnit_Framework_TestCase { 305 | 306 | /** 307 | * @test 308 | */ 309 | public function it_will_call_go(){ 310 | $dep = FunctionMocker::replace('Dep::go', 23); 311 | 312 | $sut = new SomeClass($dep); 313 | 314 | $this->assertEquals(23, $sut->someMethod()); 315 | } 316 | } 317 | ``` 318 | 319 | The `FunctionMocker::replace` method will set up the PHPUnit mock object using the `any` method, the call above is equivalent to 320 | 321 | ```php 322 | $dep->expects($this->any())->method('go')->willReturn(23); 323 | ``` 324 | 325 | An alternative to this instance method replacement exists if the need arises to replace more than one instance method in a test: 326 | 327 | ```php 328 | use tad\FunctionMocker\FunctionMocker; 329 | 330 | class SomeClassTest extends \PHPUnit_Framework_TestCase { 331 | 332 | public function dependencyTest(){ 333 | 334 | $func = function($one, $two){ 335 | return $one + $two; 336 | }; 337 | 338 | $mock = FunctionMocker::replace('Dependency') 339 | ->method('methodOne') // replace with null returning methods 340 | ->method('methodTwo', 23) // replace the method and return a value 341 | ->method('methodThree', $func) 342 | ->get(); 343 | 344 | $this->assertNull($mock->methodOne()); 345 | $this->assertEquals(23, $mock->methodTwo()); 346 | $this->assertEquals(4, $mock->methodThree(1,3)); 347 | 348 | } 349 | } 350 | ``` 351 | 352 | Not specifying any method to replace will return a mock object wher just the `__construct` method has been replaced. 353 | 354 | #### Mocking chaining methods 355 | Since version `0.2.13` it's possible mocking instance methods meant to be chained. Given the following dependency class 356 | 357 | ```php 358 | class Query { 359 | 360 | ... 361 | 362 | public funtion where($column, $condition, $constraint){ 363 | ... 364 | 365 | return $this; 366 | } 367 | 368 | public function getResults(){ 369 | return $this->results; 370 | } 371 | 372 | ... 373 | 374 | } 375 | ``` 376 | 377 | and a possible client class 378 | 379 | ```php 380 | class QueryUser{ 381 | 382 | ... 383 | 384 | public function getOne($id){ 385 | $this->query 386 | ->where('ID', '=', $id) 387 | ->where('type', '=', $this->type) 388 | ->getFirst(); 389 | } 390 | 391 | ... 392 | 393 | } 394 | ``` 395 | 396 | mocking the self-returning `where` method is possible in a test case using the `->` as return value 397 | 398 | ```php 399 | public function test_will_call_where_with_proper_args(){ 400 | // tell FunctionMocker to return the mock object itself when 401 | // the `where` method is called 402 | FunctionMocker::replace('Query::where', '->'); 403 | $query = FunctionMocker::replace('Query::getFirst', $mockResult); 404 | $sut = new QueryUser(); 405 | $sut->setQuery($query); 406 | 407 | // execute 408 | $sut->getOne(23); 409 | 410 | // verify 411 | ... 412 | } 413 | ``` 414 | 415 | #### Mocking abstract classes, interfaces and traits 416 | Relying on PHPUnit instance mocking engine FunctionMocker retains its ability to mock interfaces, abstract classes and traits; the syntax to do so is the same used to mock instance methods 417 | 418 | ```php 419 | interface SalutingInterface { 420 | public function sayHi(); 421 | } 422 | ``` 423 | 424 | the interface above can be replaced in a test like this 425 | 426 | ```php 427 | public function test_say_hi(){ 428 | $mock = FunctionMocker::replace('SalutingInterface::sayHi', 'Hello World!'); 429 | 430 | // passes 431 | $this->assertEquals('Hello World!', $mock->sayHi()); 432 | } 433 | ``` 434 | 435 | See PHPUnit docs for a more detailed approach. 436 | 437 | #### Spying instance methods 438 | The object returned by the `FunctionMocker::replace` method called on an instance method will allow for the methods specified in the "Methods" section to be used to check for calls made to the replaced method: 439 | 440 | ```php 441 | // file SomeClass.php 442 | 443 | class SomeClass{ 444 | 445 | public function methodOne(){ 446 | ... 447 | } 448 | 449 | public function methodTwo(){ 450 | ... 451 | } 452 | } 453 | 454 | // file SomeClassTest.php 455 | 456 | use tad\FunctionMocker\FunctionMocker; 457 | 458 | class SomeClassTest extends PHPUnit_Framework_TestCase { 459 | 460 | /** 461 | * @test 462 | */ 463 | public function returns_the_same_replacement_object(){ 464 | // replace both class instance methods to return 23 465 | $replacement = FunctionMocker::replace('SomeClass::methodOne', 23); 466 | // $replacement === $replacement2 467 | $replacement2 = FunctionMocker::replace('SomeClass::methodTwo', 23); 468 | 469 | $replacement->methodOne(); 470 | $replacement->methodTwo(); 471 | 472 | $replacement->wasCalledOnce('methodOne'); 473 | $replacement->wasCalledOnce('methodTwo'); 474 | } 475 | } 476 | ``` 477 | 478 | An alternative and more fluid API allows rewriting the assertions above in a way that's more similar to the one used by [prophecy](https://github.com/phpspec/prophecy "phpspec/prophecy · GitHub"): 479 | 480 | ```php 481 | // file SomeClassTest.php 482 | 483 | use tad\FunctionMocker\FunctionMocker; 484 | 485 | class SomeClassTest extends PHPUnit_Framework_TestCase { 486 | 487 | /** 488 | * @test 489 | */ 490 | public function returns_the_same_replacement_object(){ 491 | // replace both class instance methods to return 23 492 | $mock = FunctionMocker::replace('SomeClass) 493 | ->methodOne() 494 | ->methodTwo(); 495 | $replacement = $mock->get(); // think of $mock->reveal() 496 | 497 | $replacement->methodOne(); 498 | $replacement->methodTwo(); 499 | 500 | $mock->verify()->methodOne()->wasCalledOnce(); 501 | $mock->verify()->methodTwo()->wasCalledOnce(); 502 | } 503 | } 504 | ``` 505 | 506 | 507 | #### Batch replacing instance methods 508 | It's possible to batch replace instances using the same syntax used for batch function and static method replacement. 509 | Given the `SomeClass` above: 510 | 511 | ```php 512 | public function testBatchInstanceMethodReplacement(){ 513 | $methods = ['SomeClass::methodOne', 'SomeClass::methodTwo']; 514 | // replace both class instance methods to return 23 515 | $replacements = FunctionMocker::replace($methods, 23); 516 | 517 | $replacement[0]->methodOne(); 518 | $replacement[1]->methodTwo(); 519 | 520 | $replacement[0]->wasCalledOnce('methodOne'); 521 | $replacement[1]->wasCalledOnce('methodTwo'); 522 | } 523 | ``` 524 | 525 | ## Methods 526 | Beside the methods defined as part of a [PHPUnit](http://phpunit.de/) mock object interface (see [here](https://phpunit.de/manual/3.7/en/test-doubles.html)), available only when replacing instance methods, the function mocker will extend the replaced functions and methods with the following methods: 527 | 528 | * `wasCalledTimes(int $times [, string $methodName])` - will assert a PHPUnit assertion if the function or static method was called `$times` times; the `$times` parameter can come using the times syntax below. 529 | * `wasCalledOnce([string $methodName])` - will assert a PHPUnit assertion if the function or static method was called once. 530 | * `wasNotCalled([string $methodName])` - will assert a PHPUnit assertion if the function or static method was not called. 531 | * `wasCalledWithTimes(array $args, int $times[, string $methodName])` - will assert a PHPUnit assertion if the function or static method was called with `$args` arguments `$times` times; the `$times` parameter can come using the times syntax below; the `$args` parameter can be any combination of primitive values and PHPUnit constraints like `[23, Test::isInstanceOf('SomeClass')]`. 532 | * `wasCalledWithOnce(array $args[, string $methodName])` - will assert a PHPUnit assertion if the function or static method was called with `$args` arguments once; the `$args` parameter can be any combination of primitive values and PHPUnit constraints like `[23, Test::isInstanceOf('SomeClass')]`. 533 | * `wasNotCalledWith(array $args[, string $methodName])` - will assert a PHPUnit assertion if the function or static method was not called with `$args` arguments; the `$args` parameter can be any combination of primitive values and PHPUnit constraints like `[23, Test::isInstanceOf('SomeClass')]`. 534 | 535 | >The method name is needed to verify calls on replaced instance methods! 536 | 537 | ### Times 538 | When specifying the number of times a function or method should have been called a flexible syntax is available; in its most basic form can be expressed in numbers 539 | 540 | ```php 541 | // the function should have have been called exactly 2 times 542 | $function->wasCalledTimes(2); 543 | ``` 544 | 545 | but the usage of strings makes the check less cumbersome using the comparator syntax used in PHP 546 | 547 | ```php 548 | // the function should have been called at least 2 times 549 | $function->wasCalledTimes('>=2'); 550 | ``` 551 | 552 | available comparators are `>n`, `=n`, `<=n`, `==n` (same as inserting a number), `!n`. 553 | 554 | ## Sugar methods 555 | Function Mocker packs some sugar methods to make my testing life easier. The result of any of these methods can be achieved using alternative code but I've implemented those to speed things up a bit. 556 | 557 | ### Test methods 558 | Function Mocker wraps a `PHPUnit_Framework_TestCase` to allow the calling of test methods normally called on `$this` to be statically called on the `FunctionMocker` 559 | class or any of its aliases. A test method can be writte like this 560 | 561 | ```php 562 | use tad\FunctionMocker\FunctionMocker as Test; 563 | 564 | class SomeTest extends \PHPUnit_Framework_TestCase { 565 | 566 | public function test_true() { 567 | $this->assertTrue(true); 568 | } 569 | 570 | public function test_wrapped_true_work_the_same() { 571 | Test::assertTrue(true); 572 | } 573 | 574 | } 575 | ``` 576 | 577 | Being a mere wrapping the test case to be used can be set using the `setTestCase` static method in the test case `setUp` method 578 | 579 | ```php 580 | public function setUp() { 581 | FunctionMocker::setTestCase($this); 582 | } 583 | ``` 584 | 585 | and any method specific to the test case will be available as a static method of the `tad\FunctionMocker\FunctionMocker` class. 586 | Beside methods defined by the wrapped test case any method defined by the `PHPUnit_Framework_TestCase` class is available for autocompletion to comment reading IDEs like [PhpStorm](http://www.jetbrains.com/phpstorm/) or [Sublime Text](http://www.sublimetext.com/). 587 | 588 | ### Replacing a global 589 | Allows replacing a global with a mock object and restore it after the test. Best used to replace/set globally shared instances of objects to mock; e.g.: 590 | 591 | ```php 592 | FunctionMocker::replaceGlobal('wpdb', 'wpdb::get_row', $rowData); 593 | 594 | // this will access $wpdb->get_row() 595 | $post = get_latest_post(); 596 | 597 | // verify 598 | $this->assertEquals(...); 599 | ``` 600 | 601 | is the same as writing 602 | 603 | ```php 604 | // prepare 605 | $mockWpdb = FunctionMocker::replace('wpdb::get_row', $rowData); 606 | $prevWpdb = isset($GLOBALS['wpdb']) ? $GLOBALS['wpdb'] : null; 607 | $GLOBALS['wpdb'] = $mockWpdb; 608 | 609 | // this will access $wpdb->get_row() 610 | $post = get_latest_post(); 611 | 612 | // verify 613 | $this->assertEquals(...); 614 | 615 | // restore state 616 | $GLOBALS['wpdb'] = $prevWpdb; 617 | ``` 618 | 619 | ### Setting a global 620 | Allows replacing/setting a global value and restore it's state after the test. 621 | 622 | ```php 623 | FunctionMocker::setGlobal('switchingToTheme', 'foo'); 624 | $do_action = FunctionMocker::replace('do_action'); 625 | 626 | // exercitate 627 | call_switch_theme_actions(); 628 | 629 | $do_action->wasCalledWithOnce(['before_switch_to_theme_foo', ]) 630 | ``` 631 | 632 | same as writing 633 | 634 | ```php 635 | $prev = isset($GLOBALS['switchingToTheme']) ? $GLOBALS['switchingToTheme'] : null; 636 | $GLOBALS['switchingToTheme'] = 'foo'; 637 | $do_action = FunctionMocker::replace('do_action'); 638 | 639 | // exercitate 640 | call_switch_theme_actions(); 641 | 642 | // verify 643 | $do_action->wasCalledWithOnce(['before_switch_to_theme_foo', ]) 644 | 645 | // restore state 646 | $GLOBALS['switchingToTheme'] = $prev; 647 | ``` -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /phpspec.yml: -------------------------------------------------------------------------------- 1 | formatter.name: pretty 2 | suites: 3 | function_mocker_suite: 4 | namespace: tad\FunctionMocker 5 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | tests 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/functions.php: -------------------------------------------------------------------------------- 1 | is_string(); 17 | 18 | $invocation = new SpyCallLogger(); 19 | 20 | return $invocation; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/tad/FunctionMocker/Call/Logger/LoggerInterface.php: -------------------------------------------------------------------------------- 1 | constraintClass = class_exists('\PHPUnit_Framework_Constraint') ? 21 | '\PHPUnit_Framework_Constraint' 22 | : '\\PHPUnit\\Framework\\Constraint\\Constraint'; 23 | } 24 | 25 | public function called(?array $args = null) 26 | { 27 | $this->calls[] = CallTrace::fromArguments($args); 28 | } 29 | 30 | public function getCallTimes(?array $args = null) 31 | { 32 | $calls = $this->calls; 33 | if ($args) { 34 | $calls = $this->getCallsMatchingArgs($args, $calls); 35 | } 36 | 37 | return count($calls); 38 | } 39 | 40 | /** 41 | * @param array $args 42 | * @param $calls 43 | * @return array 44 | */ 45 | private function getCallsMatchingArgs(array $args, $calls) 46 | { 47 | $calls = array_filter($calls, function (CallTrace $call) use ($args) { 48 | $callArgs = $call->getArguments(); 49 | 50 | return $this->compareArgs($args, $callArgs); 51 | }); 52 | return $calls; 53 | } 54 | 55 | /** 56 | * @param $args 57 | * @param $callArgs 58 | * @return bool 59 | */ 60 | private function compareArgs(array $args, array $callArgs) 61 | { 62 | if (count($args) > count($callArgs)) { 63 | return false; 64 | } 65 | $args_count = count($args); 66 | for ($i = 0; $i < $args_count; $i++) { 67 | $arg = $args[$i]; 68 | $callArg = $callArgs[$i]; 69 | if (!$this->compareArg($arg, $callArg)) { 70 | return false; 71 | } 72 | } 73 | 74 | return true; 75 | } 76 | 77 | /** 78 | * @param $arg 79 | * @param $callArg 80 | * @return bool 81 | */ 82 | private function compareArg($arg, $callArg) 83 | { 84 | return is_a($arg, $this->constraintClass) ? $arg->evaluate($callArg, '', true) : $arg === $callArg; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/tad/FunctionMocker/Call/Verifier/AbstractVerifier.php: -------------------------------------------------------------------------------- 1 | framework = class_exists('PHPUnit_Framework_TestCase') ? 35 | 'PHPUnit_Framework_TestCase' 36 | : '\\PHPUnit\\Framework\\TestCase'; 37 | $this->constraintClass = class_exists('\PHPUnit_Framework_Constraint') ? 38 | '\PHPUnit_Framework_Constraint' 39 | : '\\PHPUnit\\Framework\\Constraint\\Constraint'; 40 | } 41 | 42 | public function wasNotCalled() 43 | { 44 | $this->wasCalledTimes(0); 45 | } 46 | 47 | public function wasCalledTimes($times) 48 | { 49 | throw new \Exception('Method not implemented'); 50 | } 51 | 52 | public function wasNotCalledWith(array $args) 53 | { 54 | $this->wasCalledWithTimes($args, 0); 55 | } 56 | 57 | public function wasCalledWithTimes(array $args, $times) 58 | { 59 | throw new \Exception('Method not implemented'); 60 | } 61 | 62 | public function wasCalledOnce() 63 | { 64 | $this->wasCalledTimes(1); 65 | } 66 | 67 | public function wasCalledWithOnce(array $args) 68 | { 69 | $this->wasCalledWithTimes($args, 1); 70 | } 71 | 72 | /** 73 | * @param \PHPUnit_Framework_MockObject_Matcher_InvokedRecorder|InvocationOrder $invokedRecorder 74 | * 75 | * @return mixed 76 | */ 77 | public function setInvokedRecorder($invokedRecorder) 78 | { 79 | $this->invokedRecorder = $invokedRecorder; 80 | } 81 | 82 | public function setRequest(ReplacementRequest $request) 83 | { 84 | $this->request = $request; 85 | } 86 | 87 | /** 88 | * @param $times 89 | * @param $callTimes 90 | * @param $functionName 91 | * 92 | * @return mixed 93 | */ 94 | protected function matchCallTimes($times, $callTimes, $functionName) 95 | { 96 | $matchingStrategy = MatchingStrategyFactory::make($times); 97 | /** @noinspection PhpUndefinedMethodInspection */ 98 | $condition = $matchingStrategy->matches($callTimes); 99 | if (!$condition) { 100 | $message = sprintf('%s was called %d times, %s times expected.', $functionName, $callTimes, $times); 101 | $this->fail($message); 102 | } 103 | 104 | $this->assertTrue($condition); 105 | 106 | return $condition; 107 | } 108 | 109 | /** 110 | * @param array $args 111 | * @param $times 112 | * @param $functionName 113 | * @param $callTimes 114 | * 115 | * @return mixed 116 | */ 117 | protected function matchCallWithTimes(array $args, $times, $functionName, $callTimes) 118 | { 119 | $matchingStrategy = MatchingStrategyFactory::make($times); 120 | /** @noinspection PhpUndefinedMethodInspection */ 121 | $condition = $matchingStrategy->matches($callTimes); 122 | if (!$condition) { 123 | $printArgs = array_map(function ($arg) { 124 | return print_r($arg, true); 125 | }, $args); 126 | $args = "[\n\t" . implode(",\n\t", $printArgs) . ']'; 127 | $message = sprintf('%s was called %d times with %s, %d times expected.', $functionName, $callTimes, $args, $times); 128 | $this->fail($message); 129 | } 130 | 131 | $this->assertTrue($condition); 132 | 133 | return $condition; 134 | } 135 | 136 | /** 137 | * @param $condition 138 | */ 139 | protected function assertTrue($condition) 140 | { 141 | call_user_func([$this->framework, 'assertTrue'], $condition); 142 | } 143 | 144 | /** 145 | * @param string $message 146 | */ 147 | protected function fail($message) 148 | { 149 | call_user_func([$this->framework, 'fail'], $message); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/tad/FunctionMocker/Call/Verifier/CallVerifierFactory.php: -------------------------------------------------------------------------------- 1 | isFunction()) { 16 | return FunctionCallVerifier::__from($checker, $returnValue, $callLogger); 17 | } 18 | if ($request->isStaticMethod()) { 19 | return StaticMethodCallVerifier::__from($checker, $returnValue, $callLogger); 20 | } 21 | 22 | return InstanceMethodCallVerifier::from($returnValue, $callLogger); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/tad/FunctionMocker/Call/Verifier/FunctionCallVerifier.php: -------------------------------------------------------------------------------- 1 | __checker = $checker; 29 | $instance->__returnValue = $returnValue; 30 | $instance->__callLogger = $callLogger; 31 | 32 | return $instance; 33 | } 34 | 35 | public function __willReturnCallable() 36 | { 37 | return $this->__returnValue->isCallable(); 38 | } 39 | 40 | public function __wasEvalCreated() 41 | { 42 | return $this->__checker->isEvalCreated(); 43 | } 44 | 45 | /** 46 | * Checks if the function or method was called the specified number 47 | * of times. 48 | * 49 | * @param int $times 50 | * 51 | * @return void 52 | */ 53 | public function wasCalledTimes($times) 54 | { 55 | 56 | /** @noinspection PhpUndefinedMethodInspection */ 57 | $callTimes = $this->__callLogger->getCallTimes(); 58 | $functionName = $this->__getFunctionName(); 59 | 60 | $this->matchCallTimes($times, $callTimes, $functionName); 61 | } 62 | 63 | public function __getFunctionName() 64 | { 65 | return $this->__checker->getFunctionName(); 66 | } 67 | 68 | /** 69 | * Checks if the function or method was called with the specified 70 | * arguments a number of times. 71 | * 72 | * @param array $args 73 | * @param int $times 74 | * 75 | * @return void 76 | */ 77 | public function wasCalledWithTimes(array $args, $times) 78 | { 79 | 80 | /** @noinspection PhpUndefinedMethodInspection */ 81 | $callTimes = $this->__callLogger->getCallTimes($args); 82 | $functionName = $this->__getFunctionName(); 83 | 84 | $this->matchCallWithTimes($args, $times, $functionName, $callTimes); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/tad/FunctionMocker/Call/Verifier/InstanceMethodCallVerifier.php: -------------------------------------------------------------------------------- 1 | returnValue = $returnValue; 20 | $instance->callLogger = $callLogger; 21 | 22 | return $instance; 23 | } 24 | 25 | public function wasNotCalled() { 26 | if ( $this instanceof InstanceMethodCallVerifier ) { 27 | $this->request->methodName = func_get_arg( 0 ); 28 | } 29 | $this->realWasCalledTimes( 0 ); 30 | } 31 | 32 | /** 33 | * @param $times 34 | */ 35 | private function realWasCalledTimes( $times ) { 36 | $callTimes = $this->getCallTimesWithArgs( $this->request->methodName ); 37 | $this->matchCallTimes( $times, $callTimes, $this->request->methodName ); 38 | } 39 | 40 | /** 41 | * @param array $args 42 | * @param string $methodName 43 | * 44 | * @return array 45 | */ 46 | protected function getCallTimesWithArgs( $methodName, ?array $args = null ) { 47 | $invocations = $this->getInvocations(); 48 | $callTimes = 0; 49 | array_map( function ( $invocation ) use ( &$callTimes, $args, $methodName ) { 50 | if ( is_array( $args ) ) { 51 | $callTimes += $this->compareName( $invocation, $methodName ) && $this->compareArgs( $invocation, $args ); 52 | } else { 53 | $callTimes += $this->compareName( $invocation, $methodName ); 54 | } 55 | }, $invocations ); 56 | 57 | return $callTimes; 58 | } 59 | 60 | /** 61 | * @return array 62 | * @throws \ReflectionException 63 | */ 64 | private function getInvocations() { 65 | $reflectionClass = new ReflectionClass( get_class( $this->invokedRecorder ) ); 66 | $parentReflectionClass = $reflectionClass->getParentClass(); 67 | 68 | $invocationsProperty = $parentReflectionClass->getProperty('invocations'); 69 | $invocationsProperty->setAccessible( true ); 70 | 71 | return $invocationsProperty->getValue( $this->invokedRecorder ); 72 | } 73 | 74 | /** 75 | * @param \PHPUnit_Framework_MockObject_Invocation_Object|Invocation $invocation 76 | * @param $methodName 77 | * 78 | * @return bool 79 | */ 80 | private function compareName( $invocation, $methodName ) { 81 | $invokedMethodName = method_exists( $invocation, 'getMethodName' ) 82 | ? $invocation->getMethodName() 83 | : $invocation->methodName; 84 | 85 | return $invokedMethodName === $methodName; 86 | } 87 | 88 | /** 89 | * @param \PHPUnit_Framework_MockObject_Invocation_Object|Invocation $invocation 90 | * @param $args 91 | * 92 | * @return bool|mixed|void 93 | */ 94 | private function compareArgs( $invocation, $args ) { 95 | $parameters = method_exists( $invocation, 'getParameters' ) 96 | ? $invocation->getParameters() 97 | : $invocation->parameters; 98 | 99 | if ( count( $args ) > count( $parameters ) ) { 100 | return false; 101 | } 102 | $count = count( $args ); 103 | for ( $i = 0; $i < $count; $i ++ ) { 104 | $arg = $args[ $i ]; 105 | $expected = $parameters[ $i ]; 106 | if ( ! $this->compare( $expected, $arg ) ) { 107 | return false; 108 | } 109 | } 110 | 111 | return true; 112 | } 113 | 114 | /** 115 | * @param $expected 116 | * @param $arg 117 | * 118 | * @return bool|mixed 119 | */ 120 | private function compare( $expected, $arg ) { 121 | if ( $arg instanceof $this->constraintClass ) { 122 | return $arg->evaluate( $expected, '', true ); 123 | } else { 124 | return $arg === $expected; 125 | } 126 | } 127 | 128 | public function wasNotCalledWith( array $args ) { 129 | if ( $this instanceof InstanceMethodCallVerifier ) { 130 | $this->request->methodName = func_get_arg( 1 ); 131 | } 132 | $this->realWasCalledWithTimes( $args, 0 ); 133 | } 134 | 135 | /** 136 | * @param array $args 137 | * @param $times 138 | */ 139 | private function realWasCalledWithTimes( array $args, $times ) { 140 | $callTimes = $this->getCallTimesWithArgs( $this->request->methodName, $args ); 141 | $this->matchCallWithTimes( $args, $times, $this->request->methodName, $callTimes ); 142 | } 143 | 144 | public function wasCalledOnce() { 145 | if ( $this instanceof InstanceMethodCallVerifier ) { 146 | $this->request->methodName = func_get_arg( 0 ); 147 | } 148 | $this->realWasCalledTimes( 1 ); 149 | } 150 | 151 | public function wasCalledWithOnce( array $args ) { 152 | if ( $this instanceof InstanceMethodCallVerifier ) { 153 | $this->request->methodName = func_get_arg( 1 ); 154 | } 155 | $this->realWasCalledWithTimes( $args, 1 ); 156 | } 157 | 158 | /** 159 | * Checks if the function or method was called the specified number 160 | * of times. 161 | * 162 | * @param int $times 163 | * 164 | * @return void 165 | */ 166 | public function wasCalledTimes( $times ) { 167 | if ( $this instanceof InstanceMethodCallVerifier ) { 168 | $this->request->methodName = func_get_arg( 1 ); 169 | } 170 | $this->realWasCalledTimes( $times ); 171 | } 172 | 173 | /** 174 | * Checks if the function or method was called with the specified 175 | * arguments a number of times. 176 | * 177 | * @param array $args 178 | * @param int $times 179 | * 180 | * @return void 181 | */ 182 | public function wasCalledWithTimes( array $args, $times ) { 183 | if ( $this instanceof InstanceMethodCallVerifier ) { 184 | $this->request->methodName = func_get_arg( 2 ); 185 | } 186 | $this->realWasCalledWithTimes( $args, $times ); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/tad/FunctionMocker/Call/Verifier/StaticMethodCallVerifier.php: -------------------------------------------------------------------------------- 1 | args = $args ? $args : array(); 14 | 15 | return $instance; 16 | } 17 | 18 | public function getArguments() 19 | { 20 | return $this->args; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/tad/FunctionMocker/Checker.php: -------------------------------------------------------------------------------- 1 | isEvalCreated = false; 14 | $instance->functionName = $functionName; 15 | $isMethod = preg_match("/^[\\w\\d_\\\\]+::[\\w\\d_]+$/", $functionName); 16 | if ( ! $isMethod && ! function_exists($functionName)) { 17 | self::createFunction($functionName); 18 | $instance->isEvalCreated = true; 19 | } 20 | 21 | return $instance; 22 | } 23 | 24 | /** 25 | * @param $functionName 26 | * 27 | * @throws \Exception 28 | */ 29 | protected static function createFunction($functionName) { 30 | $functionNamespace = self::hasNamespace($functionName) ? 31 | self::getNamespaceFrom($functionName) 32 | : ''; 33 | $functionName = self::hasNamespace($functionName) ? self::getFunctionNameFrom($functionName) : $functionName; 34 | include_once dirname(dirname(dirname(dirname(__FILE__)))) . '/src/utils.php'; 35 | create_function($functionName, trim($functionNamespace, '\\')); 36 | } 37 | 38 | /** 39 | * @param $functionName 40 | * 41 | * @return bool 42 | */ 43 | private static function hasNamespace($functionName) { 44 | $namespaceElements = explode('\\', $functionName); 45 | if (count($namespaceElements) === 1) { 46 | return false; 47 | } 48 | 49 | return true; 50 | } 51 | 52 | /** 53 | * @param $functionName 54 | * 55 | * @return string 56 | */ 57 | private static function getNamespaceFrom($functionName) { 58 | $namespaceElements = explode('\\', $functionName); 59 | array_pop($namespaceElements); 60 | 61 | return '\\' . implode('\\', $namespaceElements); 62 | } 63 | 64 | private static function getFunctionNameFrom($functionName) { 65 | $elems = explode('\\', $functionName); 66 | 67 | return array_pop($elems); 68 | } 69 | 70 | public function getFunctionName() { 71 | return $this->functionName; 72 | } 73 | 74 | public function isEvalCreated() { 75 | return $this->isEvalCreated; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/tad/FunctionMocker/Forge/InstanceMethodRequest.php: -------------------------------------------------------------------------------- 1 | is_string()->assert(class_exists($class) || interface_exists($class) || trait_exists($class), 'Class must be a defined one'); 20 | $instance = new self; 21 | $instance->requestClassName = $class; 22 | $instance->isStaticMethod = false; 23 | $instance->isInstanceMethod = true; 24 | $instance->isFunction = false; 25 | $instance->isMethod = true; 26 | $instance->methodName = ''; 27 | 28 | return $instance; 29 | } 30 | } -------------------------------------------------------------------------------- /src/tad/FunctionMocker/Forge/Step.php: -------------------------------------------------------------------------------- 1 | is_string() 43 | ->assert(class_exists($class), 'Class to getMock must be defined'); 44 | 45 | $instance = new self;; 46 | $instance->class = $class; 47 | 48 | return $instance; 49 | } 50 | 51 | public function get() 52 | { 53 | $this->setUpMockObject(); 54 | 55 | return $this->instanceForger->getWrappedMockObject($this->mockObject, $this->class, $this->request); 56 | } 57 | 58 | protected function setUpMockObject() 59 | { 60 | if (empty($this->mockObject)) { 61 | $this->mockObject = $this->getForgedMockObject(); 62 | $this->setMockObjectExpectation($this->mockObject); 63 | $this->request = InstanceMethodRequest::instance($this->class); 64 | } 65 | } 66 | 67 | /** 68 | * @return MockObject 69 | * @throws \ReflectionException 70 | */ 71 | protected function getForgedMockObject() 72 | { 73 | $methods = array_merge(array_keys($this->methods), ['__construct']); 74 | $mockObject = $this->instanceForger->getPHPUnitMockObjectFor($this->class, $methods); 75 | 76 | return $mockObject; 77 | } 78 | 79 | /** 80 | * @param $mockObject 81 | */ 82 | protected function setMockObjectExpectation(&$mockObject) 83 | { 84 | foreach ($this->methods as $method => $returnValue) { 85 | $this->instanceForger->setMockObjectExpectation($mockObject, $method, ReturnValue::from($returnValue)); 86 | } 87 | } 88 | 89 | /** 90 | * @param InstanceForger $instanceForger 91 | */ 92 | public function setInstanceForger(InstanceForger $instanceForger) 93 | { 94 | $this->instanceForger = $instanceForger; 95 | } 96 | 97 | public function setClass($class) 98 | { 99 | $this->class = $class; 100 | } 101 | 102 | public function method($methodName, $returnValue = null) 103 | { 104 | $this->methods[$methodName] = $returnValue; 105 | 106 | return $this; 107 | } 108 | 109 | public function verify() 110 | { 111 | $this->setUpMockObject(); 112 | 113 | return $this->instanceForger->getVerifyingMockObject($this->mockObject, $this->class, $this->request); 114 | } 115 | } -------------------------------------------------------------------------------- /src/tad/FunctionMocker/Forge/StepInterface.php: -------------------------------------------------------------------------------- 1 | is_string()->_or()->is_array(); 108 | if (is_array($functionName)) { 109 | $replacements = []; 110 | array_map(function ($_functionName) use ($returnValue, &$replacements) { 111 | $replacements[] = self::_replace($_functionName, $returnValue); 112 | }, $functionName); 113 | 114 | $indexedReplacements = self::getIndexedReplacements($replacements); 115 | 116 | return $indexedReplacements; 117 | } 118 | 119 | return self::_replace($functionName, $returnValue); 120 | } 121 | 122 | /** 123 | * Replaces a function, a static method or an instance method with a set of results, that will be returned in order. 124 | * 125 | * The function or methods to be replaced must be specified with fully qualified names like: 126 | * 127 | * FunctionMocker::replaceInOrder('my\name\space\aFunction', [1, 2, 3]); 128 | * FunctionMocker::replaceInOrder('my\name\space\SomeClass::someMethod', [1, 2, 3]); 129 | * 130 | * @param string $functionName The fully-qualified name of the function, or static method, to replace. 131 | * @param array $returnValues The set of values to return when the function is called, in order. 132 | * 133 | * @return mixed A return value from the set, depending on the current index. 134 | */ 135 | public static function replaceInOrder($functionName, array $returnValues) { 136 | if ( ! $returnValues || ! is_array($returnValues) ) { 137 | return self::replace($functionName, $returnValues); 138 | } 139 | 140 | $returnValues = array_values($returnValues); 141 | 142 | return self::replace($functionName, static function () use ($returnValues) { 143 | static $i = 0; 144 | 145 | return $returnValues[$i ++]; 146 | }); 147 | } 148 | 149 | /** 150 | * Internal method to replace the functions or static methods. 151 | * 152 | * @param string $functionName The fully-qualified name of the function or static method or instance method to 153 | * replace. 154 | * @param mixed $returnValue The return value for the replaced function or static method. 155 | * 156 | * @return mixed|null|Call\Verifier\InstanceMethodCallVerifier|static|Step The replaced function handle, for spying, 157 | * or the head of a replacement chain. 158 | * 159 | * @throws \Exception 160 | */ 161 | private static function _replace($functionName, $returnValue) { 162 | $request = ReplacementRequest::on($functionName); 163 | $returnValue = ReturnValue::from($returnValue); 164 | $methodName = $request->getMethodName(); 165 | 166 | if ($request->isClass()) { 167 | return self::get_instance_replacement_chain_head($functionName); 168 | } 169 | if ($request->isInstanceMethod()) { 170 | return self::get_instance_replacement($request, $returnValue); 171 | } 172 | 173 | return self::get_function_or_static_method_replacement($functionName, $returnValue, $request, $methodName); 174 | } 175 | 176 | /** 177 | * @param $className 178 | * 179 | * @return \tad\FunctionMocker\Forge\Step 180 | */ 181 | private static function get_instance_replacement_chain_head($className) { 182 | $step = new Step(); 183 | $step->setClass($className); 184 | $forger = new InstanceForger(); 185 | $forger->setTestCase(self::getTestCase()); 186 | $step->setInstanceForger($forger); 187 | 188 | return $step; 189 | } 190 | 191 | /** 192 | * @return SpoofTestCase 193 | */ 194 | public static function getTestCase() { 195 | if ( ! self::$testCase) { 196 | self::$testCase = new SpoofTestCase(); 197 | } 198 | $testCase = self::$testCase; 199 | 200 | return $testCase; 201 | } 202 | 203 | /** 204 | * @param \PHPUnit_Framework_TestCase $testCase 205 | */ 206 | public static function setTestCase($testCase) { 207 | self::$testCase = $testCase; 208 | } 209 | 210 | /** 211 | * @param ReplacementRequest $request 212 | * @param $returnValue 213 | * 214 | * @return mixed 215 | */ 216 | public static function get_instance_replacement(ReplacementRequest $request, $returnValue) { 217 | $forger = new InstanceForger(); 218 | $forger->setTestCase(self::getTestCase()); 219 | 220 | return $forger->getMock($request, $returnValue); 221 | } 222 | 223 | /** 224 | * @param $functionName 225 | * @param $returnValue 226 | * @param $request 227 | * @param $methodName 228 | * 229 | * @return Call\Verifier\InstanceMethodCallVerifier|static 230 | * @throws \Exception 231 | */ 232 | private static function get_function_or_static_method_replacement($functionName, $returnValue, $request, $methodName) { 233 | $checker = Checker::fromName($functionName); 234 | $callLogger = CallLoggerFactory::make($functionName); 235 | $verifier = CallVerifierFactory::make($request, $checker, $returnValue, $callLogger); 236 | self::replace_with_patchwork($functionName, $returnValue, $request, $methodName, $callLogger); 237 | 238 | return $verifier; 239 | } 240 | 241 | /** 242 | * @param $functionName 243 | * @param $returnValue 244 | * @param $request 245 | * @param $methodName 246 | * @param $callLogger 247 | */ 248 | private static function replace_with_patchwork($functionName, ReturnValue $returnValue, ReplacementRequest $request, $methodName, LoggerInterface $callLogger) { 249 | $functionOrMethodName = $request->isMethod() ? $methodName : $functionName; 250 | $replacementFunction = self::getReplacementFunction($functionOrMethodName, $returnValue, $callLogger); 251 | redefine($functionName, $replacementFunction); 252 | } 253 | 254 | /** 255 | * @param $functionName 256 | * @param $returnValue 257 | * @param $invocation 258 | * 259 | * @return callable 260 | */ 261 | protected static function getReplacementFunction($functionName, $returnValue, $invocation) { 262 | $replacementFunction = function () use ($functionName, $returnValue, $invocation) { 263 | $args = func_get_args(); 264 | 265 | /** @noinspection PhpUndefinedMethodInspection */ 266 | $invocation->called($args); 267 | 268 | /** @noinspection PhpUndefinedMethodInspection */ 269 | return $returnValue->isCallable() ? $returnValue->call($args) : $returnValue->getValue(); 270 | }; 271 | 272 | return $replacementFunction; 273 | } 274 | 275 | /** 276 | * @param $return 277 | * 278 | * @return array 279 | */ 280 | private static function getIndexedReplacements($return) { 281 | $indexedReplacements = []; 282 | if ($return[0] instanceof FunctionCallVerifier) { 283 | array_map(function (FunctionCallVerifier $replacement) use (&$indexedReplacements) { 284 | $fullFunctionName = $replacement->__getFunctionName(); 285 | $functionNameElements = preg_split('/(\\\\|::)/', $fullFunctionName); 286 | $functionName = array_pop($functionNameElements); 287 | $indexedReplacements[$functionName] = $replacement; 288 | }, $return); 289 | 290 | } 291 | 292 | return $indexedReplacements; 293 | } 294 | 295 | /** 296 | * Calls the original function or static method with the given arguments 297 | * and returns the return value if any. 298 | * 299 | * @param array $args 300 | * 301 | * @return mixed 302 | */ 303 | public static function callOriginal(?array $args = null) { 304 | return \Patchwork\relay($args); 305 | } 306 | 307 | /** 308 | * Replaces/sets a global object with an instance replacement of the class. 309 | * 310 | * The $GLOBALS state will be reset at the next `FunctionMocker::tearDown` call. 311 | * 312 | * @param string $globalHandle The key the value is associated to in the $GLOBALS array. 313 | * @param string $functionName A `Class::method` format string 314 | * @param mixed $returnValue The return value or callback, see `replace` method. 315 | * 316 | * @return mixed The object that's been set in the $GLOBALS array. 317 | */ 318 | public static function replaceGlobal($globalHandle, $functionName, $returnValue = null) { 319 | \Arg::_($globalHandle, 'Global var key')->is_string(); 320 | 321 | self::backupGlobal($globalHandle); 322 | 323 | $replacement = FunctionMocker::_replace($functionName, $returnValue); 324 | $GLOBALS[$globalHandle] = $replacement; 325 | 326 | return $replacement; 327 | } 328 | 329 | protected static function backupGlobal($globalHandle) { 330 | $shouldSave = ! isset(self::$globalsBackup[$globalHandle]); 331 | if ( ! $shouldSave) { 332 | return; 333 | } 334 | self::$globalsBackup[$globalHandle] = isset($GLOBALS[$globalHandle]) ? $GLOBALS[$globalHandle] : null; 335 | } 336 | 337 | /** 338 | * Sets a global value restoring the state after the test ran. 339 | * 340 | * @param string $globalHandle The key the value will be associated to in the $GLOBALS array. 341 | * @param mixed $replacement The value that will be set in the $GLOBALS array. 342 | * 343 | * @return mixed The object that's been set in the $GLOBALS array. 344 | */ 345 | public static function setGlobal($globalHandle, $replacement = null) { 346 | \Arg::_($globalHandle, 'Global var key')->is_string(); 347 | 348 | self::backupGlobal($globalHandle); 349 | 350 | $GLOBALS[$globalHandle] = $replacement; 351 | 352 | return $replacement; 353 | } 354 | 355 | public static function forge($class) { 356 | return new Step($class); 357 | } 358 | 359 | /** 360 | * Writes Patchwork configuration to file if needed. 361 | *j 362 | * @param array $options An array of options as those supported by Patchwork configuration. 363 | * @param string $destinationFolder The absolute path to the folder that will contain the cache folder and the Patchwork 364 | * configuration file. 365 | * 366 | * @return bool Whether the configuration file was written or not. 367 | * 368 | * @throws \RuntimeException If the Patchwork configuration file or the checksum file could not be written. 369 | */ 370 | public static function writePatchworkConfig(?array $options = null, $destinationFolder = '') { 371 | $options = self::getPatchworkConfiguration($options, $destinationFolder); 372 | 373 | $configFileContents = json_encode($options); 374 | $configChecksum = md5($configFileContents); 375 | $configFilePath = $destinationFolder . '/patchwork.json'; 376 | $checksumFilePath = "{$destinationFolder}/pw-cs-{$configChecksum}.yml"; 377 | 378 | if (file_exists($configFilePath) && file_exists($checksumFilePath)) { 379 | return false; 380 | } 381 | 382 | if (false === file_put_contents($configFilePath, $configFileContents)) { 383 | throw new \RuntimeException("Could not write Patchwork library configuration file to {$configFilePath}"); 384 | } 385 | 386 | foreach (glob($destinationFolder. '/pw-cs-*.yml') as $file) { 387 | unlink($file); 388 | } 389 | 390 | $date = date('Y-m-d H:i:s'); 391 | $checksumFileContents = <<< YAML 392 | generator: FunctionMocker 393 | date: $date 394 | checksum: $configChecksum 395 | for: $configFilePath 396 | YAML; 397 | 398 | if (false === file_put_contents($checksumFilePath, $checksumFileContents)) { 399 | throw new \RuntimeException("Could not write Patchwork library configuration checksum file to {$checksumFilePath}"); 400 | } 401 | 402 | return true; 403 | } 404 | 405 | /** 406 | * Return the Patchwork configuration that should be written to file. 407 | * 408 | * @param array $options An array of options as those supported by Patchwork configuration. 409 | * @param string $destinationFolder The absolute path to the folder that will contain the cache folder and the Patchwork 410 | * configuration file. 411 | * 412 | * @return array 413 | */ 414 | public static function getPatchworkConfiguration($options = [], $destinationFolder = '') { 415 | $translatedFields = ['include' => 'whitelist', 'exclude' => 'blacklist']; 416 | 417 | foreach ($translatedFields as $from => $to) { 418 | if (!empty($options[$from]) && empty($options[$to])) { 419 | $options[$to] = $options[$from]; 420 | } 421 | unset($options[$from]); 422 | } 423 | 424 | // but always exclude function-mocker and Patchwork themselves 425 | $defaultExcluded = [$destinationFolder, Utils::getVendorDir('antecedent/patchwork')]; 426 | $defaultIncluded = [$destinationFolder . '/src/utils.php']; 427 | $options['blacklist'] = !empty($options['blacklist']) 428 | ? array_merge((array) $options['blacklist'], $defaultExcluded) 429 | : $defaultExcluded; 430 | 431 | $options['whitelist'] = !empty($options['whitelist']) 432 | ? array_merge((array) $options['whitelist'], $defaultIncluded) 433 | : $defaultIncluded; 434 | 435 | if (empty($options['cache-path'])) { 436 | $options['cache-path'] = $destinationFolder . DIRECTORY_SEPARATOR . 'cache'; 437 | } 438 | 439 | $options = self::makePathsUniform( $options ); 440 | 441 | return $options; 442 | } 443 | 444 | /** 445 | * Replace '/' in paths by DIRECTORY_SEPARATOR to work properly on Windows. 446 | * 447 | * @param array $options An array of options as those supported by Patchwork configuration. 448 | * 449 | * @return array 450 | */ 451 | private static function makePathsUniform( $options ) { 452 | $pathArrays = [ 'whitelist', 'blacklist', 'cache-path' ]; 453 | 454 | foreach ( $pathArrays as $pathArray ) { 455 | $options[$pathArray] = str_replace( '/', DIRECTORY_SEPARATOR, $options[$pathArray] ); 456 | } 457 | 458 | return $options; 459 | } 460 | } 461 | -------------------------------------------------------------------------------- /src/tad/FunctionMocker/MatchingStrategy/AbstractMatchingStrategy.php: -------------------------------------------------------------------------------- 1 | is_int(); 14 | 15 | $instance = new static(); 16 | $instance->times = $times; 17 | 18 | return $instance; 19 | } 20 | 21 | public function matches($times) 22 | { 23 | throw new \RuntimeException('Method is not defined'); 24 | } 25 | 26 | public function __toString() 27 | { 28 | throw new \RuntimeException('Method is not defined'); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/tad/FunctionMocker/MatchingStrategy/AtLeastMatchingStrategy.php: -------------------------------------------------------------------------------- 1 | = $this->times; 12 | } 13 | 14 | public function __toString() 15 | { 16 | return sprintf('at least %d', $this->times); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/tad/FunctionMocker/MatchingStrategy/AtMostMatchingStrategy.php: -------------------------------------------------------------------------------- 1 | times; 12 | } 13 | 14 | public function __toString() 15 | { 16 | return sprintf('at most %d', $this->times); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/tad/FunctionMocker/MatchingStrategy/EqualMatchingStrategy.php: -------------------------------------------------------------------------------- 1 | times === $times; 10 | } 11 | 12 | public function __toString() 13 | { 14 | return sprintf('exactly %d', $this->times); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/tad/FunctionMocker/MatchingStrategy/GreaterThanMatchingStrategy.php: -------------------------------------------------------------------------------- 1 | $this->times; 12 | } 13 | 14 | public function __toString() 15 | { 16 | return sprintf('more than %d', $this->times); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/tad/FunctionMocker/MatchingStrategy/LessThanMatchingStrategy.php: -------------------------------------------------------------------------------- 1 | times; 12 | } 13 | 14 | public function __toString() 15 | { 16 | return sprintf('less than %d', $this->times); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/tad/FunctionMocker/MatchingStrategy/MatchingStrategyFactory.php: -------------------------------------------------------------------------------- 1 | is_int()->_or()->is_string(); 14 | 15 | if (is_numeric($times)) { 16 | $times = (int)$times; 17 | 18 | return EqualMatchingStrategy::on($times); 19 | } 20 | 21 | $matches = array(); 22 | if (!preg_match("/(>|>=|<|<=|==|!=)*(\\d)+/uU", $times, $matches)) { 23 | 24 | throw new \InvalidArgumentException('If times is a string it must follow the pattern [==|!=|<=|<|>=|>]*\d+'); 25 | } 26 | 27 | $prefix = $matches[1]; 28 | $times = (int)$matches[2]; 29 | 30 | switch ($prefix) { 31 | case '>': 32 | $matchingStrategy = GreaterThanMatchingStrategy::on($times); 33 | break; 34 | case '>=': 35 | $matchingStrategy = AtLeastMatchingStrategy::on($times); 36 | break; 37 | case '==': 38 | $matchingStrategy = EqualMatchingStrategy::on($times); 39 | break; 40 | case '<': 41 | $matchingStrategy = LessThanMatchingStrategy::on($times); 42 | break; 43 | case '<=': 44 | $matchingStrategy = AtMostMatchingStrategy::on($times); 45 | break; 46 | case '!=': 47 | $matchingStrategy = NotEqualMatchingStrategy::on($times); 48 | break; 49 | default: 50 | $matchingStrategy = EqualMatchingStrategy::on($times); 51 | break; 52 | } 53 | 54 | return $matchingStrategy; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/tad/FunctionMocker/MatchingStrategy/MatchingStrategyInterface.php: -------------------------------------------------------------------------------- 1 | times; 11 | } 12 | 13 | public function __toString() 14 | { 15 | return sprintf('any number but not %d', $this->times); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/tad/FunctionMocker/Method/Verifier.php: -------------------------------------------------------------------------------- 1 | returnValue, $this->callLogger); 33 | } 34 | } -------------------------------------------------------------------------------- /src/tad/FunctionMocker/MockWrapper.php: -------------------------------------------------------------------------------- 1 | originalClassName = $originalClassName; 40 | $this->classTemplate = $classTemplate ?: new ClassTemplate(); 41 | $this->methodCode = $methodCode ?: new MethodCode(); 42 | } 43 | 44 | public function getWrappedObject() 45 | { 46 | return $this->wrappedObject; 47 | } 48 | 49 | /** 50 | * @param \PHPUnit_Framework_MockObject_MockObject|MockObject $mockObject 51 | * @param \PHPUnit_Framework_MockObject_Matcher_InvokedRecorder|InvocationOrder $invokedRecorder 52 | * @param ReplacementRequest $request 53 | * 54 | * @return mixed 55 | */ 56 | public function wrap($mockObject, $invokedRecorder, ReplacementRequest $request) 57 | { 58 | $extender = new SpyExtender(); 59 | 60 | return $this->getWrappedInstance($mockObject, $extender, $invokedRecorder, $request); 61 | } 62 | 63 | /** 64 | * @param \PHPUnit_Framework_MockObject_MockObject|MockObject $object 65 | * @param ExtenderInterface $extender 66 | * 67 | * @param \PHPUnit_Framework_MockObject_Matcher_InvokedRecorder|InvocationOrder $invokedRecorder 68 | * @param ReplacementRequest $request 69 | * 70 | * @return mixed 71 | * @throws \Exception 72 | * 73 | */ 74 | protected function getWrappedInstance($object, ExtenderInterface $extender, $invokedRecorder = null, ?ReplacementRequest $request = null) 75 | { 76 | $mockClassName = get_class($object); 77 | $extendClassName = sprintf('%s_%s', uniqid('Extended_'), $mockClassName); 78 | /** @noinspection PhpUndefinedMethodInspection */ 79 | $extenderClassName = $extender->getExtenderClassName(); 80 | 81 | if (!class_exists($extendClassName)) { 82 | $classTemplate = $this->classTemplate; 83 | $template = $classTemplate->getExtendedMockTemplate(); 84 | 85 | /** @noinspection PhpUndefinedMethodInspection */ 86 | $interfaceName = $extender->getExtenderInterfaceName(); 87 | /** @noinspection PhpUndefinedMethodInspection */ 88 | $extendedMethods = $extender->getExtendedMethodCallsAndNames(); 89 | 90 | $extendedMethodsCode = array(); 91 | array_walk($extendedMethods, function ($methodName, $call) use (&$extendedMethodsCode, $classTemplate) { 92 | $methodCodeTemplate = $classTemplate->getExtendedMethodTemplate($methodName); 93 | $code = preg_replace('/%%methodName%%/', $methodName, $methodCodeTemplate); 94 | $code = preg_replace('/%%call%%/', $call, $code); 95 | $extendedMethodsCode[] = $code; 96 | }); 97 | $extendedMethodsCode = implode("\n", $extendedMethodsCode); 98 | 99 | $methodCode = $this->methodCode; 100 | $methodCode->setTargetClass($this->originalClassName); 101 | $originalMethodsCode = $methodCode->getAllMockCallings(); 102 | 103 | $classCode = preg_replace('/%%extendedClassName%%/', $extendClassName, $template); 104 | $classCode = preg_replace('/%%mockClassName%%/', $mockClassName, $classCode); 105 | $classCode = preg_replace('/%%interfaceName%%/', $interfaceName, $classCode); 106 | $classCode = preg_replace('/%%extenderClassName%%/', $extenderClassName, $classCode); 107 | $classCode = preg_replace('/%%extendedMethods%%/', $extendedMethodsCode, $classCode); 108 | $classCode = preg_replace('/%%originalMethods%%/', $originalMethodsCode, $classCode); 109 | 110 | $ok = eval($classCode); 111 | 112 | if ($ok === false) { 113 | throw new \Exception('There was a problem evaluating the code'); 114 | } 115 | } 116 | 117 | $reflectionClass = new \ReflectionClass($extendClassName); 118 | $wrapperInstance = $reflectionClass->newInstanceWithoutConstructor(); 119 | 120 | /** @noinspection PhpUndefinedMethodInspection */ 121 | $wrapperInstance->__set_functionMocker_originalMockObject($object); 122 | $callHandler = new $extenderClassName; 123 | if ($invokedRecorder) { 124 | /** @noinspection PhpUndefinedMethodInspection */ 125 | $callHandler->setInvokedRecorder($invokedRecorder); 126 | /** @noinspection PhpUndefinedMethodInspection */ 127 | $wrapperInstance->__set_functionMocker_invokedRecorder($invokedRecorder); 128 | } 129 | if ($request) { 130 | /** @noinspection PhpUndefinedMethodInspection */ 131 | $callHandler->setRequest($request); 132 | } 133 | /** @noinspection PhpUndefinedMethodInspection */ 134 | $wrapperInstance->__set_functionMocker_callHandler($callHandler); 135 | 136 | return $wrapperInstance; 137 | } 138 | 139 | public function setOriginalClassName($className) 140 | { 141 | \Arg::_($className, "Original class name")->is_string() 142 | ->assert(class_exists($className) || interface_exists($className) || trait_exists($className), 'Original class, interface or trait must be defined'); 143 | 144 | $this->originalClassName = $className; 145 | } 146 | 147 | public function setClassTemplate(ClassTemplateInterface $classTemplate) 148 | { 149 | $this->classTemplate = $classTemplate; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/tad/FunctionMocker/ReplacementRequest.php: -------------------------------------------------------------------------------- 1 | is_string(); 46 | 47 | $type = self::getType($mockRequest); 48 | 49 | return self::getInstanceForTypeAndRequest($type, $mockRequest); 50 | } 51 | 52 | /** 53 | * @param $mockRequest 54 | * 55 | * @return string 56 | */ 57 | private static function getType($mockRequest) 58 | { 59 | if (class_exists($mockRequest) || interface_exists($mockRequest) || trait_exists($mockRequest)) { 60 | return 'class'; 61 | } 62 | if (preg_match("/^[\\w\\\\_]*(::|->)[\\w\\d_]+/um", $mockRequest)) { 63 | return 'method'; 64 | } 65 | 66 | return 'function'; 67 | } 68 | 69 | /** 70 | * @param $type 71 | * @param $mockRequest 72 | * 73 | * @return ReplacementRequest 74 | */ 75 | private static function getInstanceForTypeAndRequest($type, $mockRequest) 76 | { 77 | $instance = new self; 78 | switch ($type) { 79 | case 'class': { 80 | $instance->isFunction = false; 81 | $instance->isClass = true; 82 | $instance->isStaticMethod = false; 83 | $instance->isMethod = false; 84 | $instance->isInstanceMethod = false; 85 | $instance->requestClassName = $mockRequest; 86 | $instance->methodName = false; 87 | break; 88 | } 89 | case 'method': { 90 | $request = preg_split('/(::|->)/', $mockRequest); 91 | $className = $request[0]; 92 | $methodName = $request[1]; 93 | $reflection = new \ReflectionMethod($className, $methodName); 94 | 95 | $instance->isFunction = false; 96 | $instance->isClass = false; 97 | $instance->isMethod = true; 98 | $instance->isInstanceMethod = !$reflection->isStatic(); 99 | 100 | $instance->ensure_matching_symbol($mockRequest); 101 | 102 | $instance->isStaticMethod = $reflection->isStatic(); 103 | $instance->requestClassName = $reflection->class; 104 | $instance->methodName = $reflection->name; 105 | break; 106 | } 107 | case 'function': { 108 | $instance->isFunction = true; 109 | $instance->isClass = false; 110 | $instance->isMethod = false; 111 | $instance->isStaticMethod = false; 112 | $instance->isInstanceMethod = false; 113 | $instance->requestClassName = ''; 114 | $instance->methodName = $mockRequest; 115 | break; 116 | } 117 | } 118 | 119 | return $instance; 120 | } 121 | 122 | /** 123 | * @param $requestString 124 | */ 125 | private function ensure_matching_symbol($requestString) 126 | { 127 | $m = []; 128 | preg_match('/(::|->)/', $requestString, $m); 129 | $symbol = $m[1]; 130 | if ($symbol === '->' && !$this->isInstanceMethod()) { 131 | throw new \InvalidArgumentException('Request was for a static method but the \'->\' symbol was used; keep it clear.'); 132 | } 133 | } 134 | 135 | public function isInstanceMethod() 136 | { 137 | return $this->isMethod && $this->isInstanceMethod; 138 | } 139 | 140 | public function isFunction() 141 | { 142 | return $this->isFunction; 143 | } 144 | 145 | public function isStaticMethod() 146 | { 147 | return $this->isMethod && $this->isStaticMethod; 148 | } 149 | 150 | public function isMethod() 151 | { 152 | return $this->isMethod; 153 | } 154 | 155 | public function getClassName() 156 | { 157 | return $this->requestClassName; 158 | } 159 | 160 | public function getMethodName() 161 | { 162 | return $this->methodName; 163 | } 164 | 165 | public function isClass() 166 | { 167 | return $this->isClass; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/tad/FunctionMocker/Replacers/InstanceForger.php: -------------------------------------------------------------------------------- 1 | getClassName(); 37 | $methodName = $request->getMethodName(); 38 | 39 | $methods = ['__construct', $methodName]; 40 | 41 | $mockObject = $this->getPHPUnitMockObjectFor($className, $methods); 42 | $this->setMockObjectExpectation($mockObject, $methodName, $returnValue); 43 | 44 | $wrapperInstance = $this->getWrappedMockObject($mockObject, $className, $request); 45 | 46 | return $wrapperInstance; 47 | } 48 | 49 | /** 50 | * @param $className 51 | * @param array $methods 52 | * 53 | * @return MockObject|\PHPUnit_Framework_MockObject_MockObject 54 | * @throws \ReflectionException 55 | */ 56 | public function getPHPUnitMockObjectFor($className, array $methods) 57 | { 58 | $rc = new \ReflectionClass($className); 59 | 60 | $mockObject = null; 61 | 62 | if ($rc->isInterface()) { 63 | $mockObject = $this->testCase->getMockBuilder($className) 64 | ->getMock(); 65 | } elseif ($rc->isAbstract()) { 66 | $mockObject = $this->testCase->getMockBuilder($className)->disableOriginalConstructor() 67 | ->setMethods($methods)->getMockForAbstractClass(); 68 | } elseif ($rc->isTrait()) { 69 | $mockObject = $this->testCase->getMockBuilder($className)->disableOriginalConstructor() 70 | ->setMethods($methods)->getMockForTrait(); 71 | } else { 72 | $mockBuilder = $this->testCase->getMockBuilder($className) 73 | ->disableOriginalConstructor() 74 | ->disableOriginalClone() 75 | ->disableArgumentCloning(); 76 | 77 | // removed in later versions of PHPUnit 78 | if (method_exists($mockBuilder, 'disallowMockingUnknownTypes')) { 79 | $mockBuilder->disallowMockingUnknownTypes(); 80 | } 81 | 82 | $mockObject = $mockBuilder->getMock(); 83 | } 84 | 85 | return $mockObject; 86 | } 87 | 88 | /** 89 | * @param \PHPUnit\Framework\MockObject\MockObject $mockObject 90 | * @param $methodName 91 | * @param ReturnValue|null $returnValue 92 | */ 93 | public function setMockObjectExpectation(&$mockObject, $methodName, ?ReturnValue $returnValue = null) 94 | { 95 | if ($returnValue->isCallable()) { 96 | // callback 97 | $mockObject->expects($this->invokedRecorder)->method($methodName) 98 | ->willReturnCallback($returnValue->getValue()); 99 | } else if ($returnValue->isSelf()) { 100 | // -> 101 | $mockObject->expects($this->invokedRecorder)->method($methodName)->willReturn($mockObject); 102 | } else { 103 | // value 104 | $mockObject->expects($this->invokedRecorder)->method($methodName) 105 | ->willReturn($returnValue->getValue()); 106 | } 107 | } 108 | 109 | /** 110 | * @param $mockObject 111 | * @param $className 112 | * @param ReplacementRequest $request 113 | * @param bool $verifying The type of wrapping to return. 114 | * 115 | * @return mixed 116 | */ 117 | public function getWrappedMockObject($mockObject, $className, ReplacementRequest $request, $verifying = false) 118 | { 119 | $hash = spl_object_hash($mockObject); 120 | 121 | $wrapperInstance = null; 122 | 123 | if ($verifying && isset($this->wrappedMockObjectsCache[$hash])) { 124 | return $this->wrappedMockObjectsCache[$hash]; 125 | } 126 | 127 | $classTemplate = $verifying ? new VerifyingClassTemplate() : new ClassTemplate(); 128 | $methodCode = $verifying ? new LoggingMethodCode() : new MethodCode(); 129 | $mockWrapper = new MockWrapper($className, $classTemplate, $methodCode); 130 | $wrapperInstance = $mockWrapper->wrap($mockObject, $this->invokedRecorder, $request); 131 | 132 | if ($verifying) { 133 | $this->wrappedMockObjectsCache[$hash] = $wrapperInstance; 134 | } 135 | 136 | return $wrapperInstance; 137 | } 138 | 139 | /** 140 | * @param \PHPUnit_Framework_TestCase|\PHPUnit\Framework\TestCase $testCase 141 | */ 142 | public function setTestCase($testCase) 143 | { 144 | $this->testCase = $testCase; 145 | $this->invokedRecorder = $this->testCase->any(); 146 | } 147 | 148 | public function getVerifyingMockObject($mockObject, $class, $request) 149 | { 150 | return $this->getWrappedMockObject($mockObject, $class, $request, true); 151 | } 152 | } -------------------------------------------------------------------------------- /src/tad/FunctionMocker/ReturnValue.php: -------------------------------------------------------------------------------- 1 | value = $returnValue; 18 | $instance->isCallable = is_callable($instance->value); 19 | $instance->isNull = is_null($returnValue); 20 | $instance->isSelf = is_string($returnValue) && '->' === $returnValue; 21 | $instance->isValue = !($instance->isCallable || $instance->isNull); 22 | 23 | return $instance; 24 | } 25 | 26 | public function isCallable() 27 | { 28 | return $this->isCallable; 29 | } 30 | 31 | public function isValue() 32 | { 33 | return $this->isValue; 34 | } 35 | 36 | public function getValue() 37 | { 38 | return $this->value; 39 | } 40 | 41 | public function isNull() 42 | { 43 | return $this->isNull; 44 | } 45 | 46 | public function call(array $args = array()) 47 | { 48 | return call_user_func_array( $this->value, $args ); 49 | } 50 | 51 | public function isSelf() 52 | { 53 | return $this->isSelf; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/tad/FunctionMocker/SpoofTestCase.php: -------------------------------------------------------------------------------- 1 | __functionMocker_callHandler = \$callHandler; 19 | } 20 | 21 | public function __get_functionMocker_CallHandler(){ 22 | return \$this->__functionMocker_callHandler; 23 | } 24 | 25 | public function __set_functionMocker_originalMockObject(\$mockObject){ 26 | \$this->__functionMocker_originalMockObject = \$mockObject; 27 | } 28 | 29 | public function __set_functionMocker_invokedRecorder(\$invokedRecorder){ 30 | \$this->__functionMocker_invokedRecorder = \$invokedRecorder; 31 | } 32 | 33 | public function __get_functionMocker_invokedRecorder(){ 34 | return \$this->__functionMocker_invokedRecorder; 35 | } 36 | 37 | %%extendedMethods%% 38 | 39 | %%originalMethods%% 40 | } 41 | CODESET; 42 | } 43 | 44 | public function getExtendedMethodTemplate($methodName) 45 | { 46 | return <<< CODESET 47 | public function %%call%%{ 48 | call_user_func_array(array(\$this->__functionMocker_callHandler, '%%methodName%%'), func_get_args()); 49 | return \$this; 50 | } 51 | 52 | CODESET; 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/tad/FunctionMocker/Template/ClassTemplateInterface.php: -------------------------------------------------------------------------------- 1 | extenderClassName; 22 | } 23 | 24 | public function getExtenderInterfaceName() 25 | { 26 | return $this->extenderInterfaceName; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/tad/FunctionMocker/Template/Extender/ExtenderInterface.php: -------------------------------------------------------------------------------- 1 | 'wasCalledTimes', 16 | 'wasCalledWithTimes(array $args = array(), $times = 0)' => 'wasCalledWithTimes', 17 | 'wasNotCalled()' => 'wasNotCalled', 18 | 'wasNotCalledWith(array $args = array())' => 'wasNotCalledWith', 19 | 'wasCalledWithOnce(array $args = array())' => 'wasCalledWithOnce', 20 | 'wasCalledOnce()' => 'wasCalledOnce' 21 | ); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/tad/FunctionMocker/Template/LoggingMethodCode.php: -------------------------------------------------------------------------------- 1 | __functionMocker_methodName = '$methodName'; 13 | \$this->__functionMocker_methodArgs = func_get_args(); 14 | 15 | return \$this; 16 | CODESET; 17 | 18 | return $body; 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /src/tad/FunctionMocker/Template/MethodCode.php: -------------------------------------------------------------------------------- 1 | targetClass = $targetClass; 29 | $this->reflection = new \ReflectionClass($targetClass); 30 | $this->methods = $this->reflection->getMethods(\ReflectionMethod::IS_PUBLIC); 31 | $fileName = $this->reflection->getFileName(); 32 | if (file_exists($fileName)) { 33 | $this->contents = file_get_contents($fileName); 34 | } 35 | 36 | return $this; 37 | } 38 | 39 | public function getTemplateFrom($methodName) 40 | { 41 | $body = '%%pre%% %%body%% %%post%%'; 42 | 43 | return $this->getMethodCodeForWithBody($methodName, $body); 44 | } 45 | 46 | /** 47 | * @param $methodName 48 | * 49 | * @param $body 50 | * 51 | * @return array|mixed|string 52 | */ 53 | protected function getMethodCodeForWithBody($methodName, $body) 54 | { 55 | $code = $this->getMethodCode($methodName); 56 | 57 | $code = $this->replaceBody($body, $code); 58 | 59 | return $code; 60 | } 61 | 62 | /** 63 | * @param $methodName 64 | * 65 | * @return array|string 66 | */ 67 | protected function getMethodCode($methodName) 68 | { 69 | $method = is_a($methodName, '\ReflectionMethod') ? $methodName : new \ReflectionMethod($this->targetClass, $methodName); 70 | 71 | $declaringClass = $method->getDeclaringClass(); 72 | $notTargetClass = $declaringClass->name != $this->targetClass; 73 | if ($notTargetClass) { 74 | $method = new \ReflectionMethod($declaringClass->name, $methodName); 75 | $contents = file_get_contents($method->getFileName()); 76 | } else { 77 | $contents = $this->contents; 78 | } 79 | 80 | $contents = str_replace( "\r\n", "\n", $contents ); 81 | $contents = str_replace( "\r", "\n", $contents ); 82 | 83 | $startLine = $method->getStartLine(); 84 | $endLine = $method->getEndLine(); 85 | 86 | $classAliases = []; 87 | $lines = explode("\n", $contents); 88 | foreach ($lines as $line) { 89 | $frags = explode(' ', $line); 90 | if (!empty($frags) && $frags[0] == 'use') { 91 | $fullClassName = $frags[1]; 92 | // use Acme\Class as Alias 93 | if (count($frags) > 2) { 94 | $alias = $frags[3]; 95 | } else { 96 | if (strpos($frags[1], '\\')) { 97 | $classNameFrags = explode('\\', $frags[1]); 98 | $alias = array_pop($classNameFrags); 99 | } else { 100 | $alias = $frags[1]; 101 | } 102 | } 103 | $alias = trim($alias, ';'); 104 | $classAliases[$alias] = trim($fullClassName, ';'); 105 | } 106 | } 107 | 108 | $lines = array_map(function ($line) use ($classAliases) { 109 | foreach ($classAliases as $classAlias => $fullClassName) { 110 | $line = str_replace($classAlias, $fullClassName, $line); 111 | } 112 | return trim($line); 113 | }, $lines); 114 | 115 | $code = array_splice($lines, $startLine - 1, $endLine - $startLine + 1); 116 | 117 | $code[0] = preg_replace('/\\s*abstract\\s*/', '', $code[0]); 118 | 119 | $code = implode(" ", $code); 120 | 121 | return $code; 122 | } 123 | 124 | /** 125 | * @param $body 126 | * @param $code 127 | * 128 | * @return mixed 129 | */ 130 | protected function replaceBody($body, $code) 131 | { 132 | $code = preg_replace('/\\{.*\\}$|;$/', '{' . $body . '}', $code); 133 | $code = preg_replace('/\\(\\s+/', '(', $code); 134 | $code = preg_replace('/\\s+\\)/', ')', $code); 135 | 136 | return $code; 137 | } 138 | 139 | public function getAllMockCallings() 140 | { 141 | $code = array_map(function ($method) { 142 | return $this->getMockCallingFrom($method); 143 | }, $this->methods); 144 | $code = implode("\n\n\t", $code); 145 | 146 | return $code; 147 | } 148 | 149 | public function getMockCallingFrom($methodName) 150 | { 151 | $method = is_a($methodName, '\ReflectionMethod') ? $methodName : new \ReflectionMethod($this->targetClass, $methodName); 152 | $methodName = is_string($methodName) ? $methodName : $method->name; 153 | $args = array_map(function (\ReflectionParameter $parameter) { 154 | return '$' . $parameter->name; 155 | }, $method->getParameters()); 156 | $args = implode(', ', $args); 157 | $body = $this->getCallBody($methodName, $args); 158 | 159 | return $this->getMethodCodeForWithBody($methodName, $body); 160 | } 161 | 162 | /** 163 | * @param $methodName 164 | * @param $args 165 | * @return string 166 | */ 167 | protected function getCallBody($methodName, $args) 168 | { 169 | $body = "return \$this->__functionMocker_originalMockObject->$methodName($args);"; 170 | return $body; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/tad/FunctionMocker/Template/MethodCodeInterface.php: -------------------------------------------------------------------------------- 1 | __functionMocker_callHandler = \$callHandler; 21 | } 22 | 23 | public function __get_functionMocker_CallHandler(){ 24 | return \$this->__functionMocker_callHandler; 25 | } 26 | 27 | public function __set_functionMocker_originalMockObject(\$mockObject){ 28 | \$this->__functionMocker_originalMockObject = \$mockObject; 29 | } 30 | 31 | public function __set_functionMocker_invokedRecorder(\$invokedRecorder){ 32 | \$this->__functionMocker_invokedRecorder = \$invokedRecorder; 33 | } 34 | 35 | public function __get_functionMocker_invokedRecorder(){ 36 | return \$this->__functionMocker_invokedRecorder; 37 | } 38 | 39 | %%extendedMethods%% 40 | 41 | %%originalMethods%% 42 | } 43 | CODESET; 44 | } 45 | 46 | public function getExtendedMethodTemplate($methodName) 47 | { 48 | $map = [ 49 | 'wasCalledTimes' => 'wasCalledTimesTemplate', 50 | 'wasCalledWithTimes' => 'wasCalledWithTimesTemplate', 51 | 'wasNotCalled' => 'wasNotCalledTemplate', 52 | 'wasCalledOnce' => 'wasCalledOnceTemplate', 53 | ]; 54 | if (array_key_exists($methodName, $map)) { 55 | return $this->{$map[$methodName]}(); 56 | } 57 | return <<< CODESET 58 | public function %%call%%{ 59 | \$args = [func_get_args(),\$this->__functionMocker_methodName]; 60 | call_user_func_array(array(\$this->__functionMocker_callHandler, '%%methodName%%'), \$args); 61 | } 62 | 63 | CODESET; 64 | } 65 | 66 | protected function wasCalledTimesTemplate() 67 | { 68 | return <<< CODESET 69 | public function wasCalledTimes(\$times){ 70 | if(empty(\$this->__functionMocker_methodArgs)){ 71 | \$args = [ 72 | \$times, 73 | \$this->__functionMocker_methodName 74 | ]; 75 | \$call = 'wasCalledTimes'; 76 | } else { 77 | \$args = [ 78 | \$this->__functionMocker_methodArgs, 79 | \$times, 80 | \$this->__functionMocker_methodName 81 | ]; 82 | \$call = 'wasCalledWithTimes'; 83 | } 84 | call_user_func_array(array(\$this->__functionMocker_callHandler, \$call), \$args); 85 | } 86 | CODESET; 87 | } 88 | 89 | protected function wasCalledWithTimesTemplate() 90 | { 91 | return <<< CODESET 92 | public function wasCalledWithTimes(array \$args = array(), \$times = 0){ 93 | \$args = [ 94 | \$args, 95 | \$times, 96 | \$this->__functionMocker_methodName 97 | ]; 98 | call_user_func_array(array(\$this->__functionMocker_callHandler, 'wasCalledWithTimes'), \$args); 99 | } 100 | CODESET; 101 | 102 | } 103 | 104 | protected function wasNotCalledTemplate() 105 | { 106 | return <<< CODESET 107 | public function wasNotCalled(){ 108 | if(empty(\$this->__functionMocker_methodArgs)){ 109 | \$args = [ 110 | \$this->__functionMocker_methodName 111 | ]; 112 | \$call = 'wasNotCalled'; 113 | } else { 114 | \$args = [ 115 | \$this->__functionMocker_methodArgs, 116 | \$this->__functionMocker_methodName 117 | ]; 118 | \$call = 'wasNotCalledWith'; 119 | } 120 | call_user_func_array(array(\$this->__functionMocker_callHandler, \$call), \$args); 121 | } 122 | CODESET; 123 | 124 | } 125 | 126 | protected function wasNotCalledWithTemplate() 127 | { 128 | return <<< CODESET 129 | public function wasNotCalledWith(array \$args = array()){ 130 | \$args = [ 131 | \$args, 132 | \$this->__functionMocker_methodName 133 | ]; 134 | call_user_func_array(array(\$this->__functionMocker_callHandler, 'wasNotCalledWith'), \$args); 135 | } 136 | 137 | CODESET; 138 | 139 | } 140 | 141 | protected function wasCalledWithOnceTemplate() 142 | { 143 | return <<< CODESET 144 | public function wasCalledWithOnce(array \$args = array()){ 145 | \$args = [ 146 | \$args, 147 | \$this->__functionMocker_methodName 148 | ]; 149 | call_user_func_array(array(\$this->__functionMocker_callHandler, 'wasCalledWithOnce'), \$args); 150 | } 151 | CODESET; 152 | } 153 | 154 | protected function wasCalledOnceTemplate() 155 | { 156 | return <<< CODESET 157 | public function wasCalledOnce(){ 158 | if(empty(\$this->__functionMocker_methodArgs)){ 159 | \$args = [ 160 | \$this->__functionMocker_methodName 161 | ]; 162 | \$call = 'wasCalledOnce'; 163 | }else{ 164 | \$args = [ 165 | \$this->__functionMocker_methodArgs, 166 | \$this->__functionMocker_methodName 167 | ]; 168 | \$call = 'wasCalledWithOnce'; 169 | } 170 | call_user_func_array(array(\$this->__functionMocker_callHandler, \$call), \$args); 171 | } 172 | CODESET; 173 | 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/tad/FunctionMocker/Utils.php: -------------------------------------------------------------------------------- 1 | assert(is_dir($rootDir), 'Root dir must be an existing dir'); 16 | 17 | $_list = array_map(function ($frag) use ($rootDir) { 18 | $path = $rootDir . DIRECTORY_SEPARATOR . self::normalizePathFrag($frag); 19 | 20 | return file_exists($path) ? $path : null; 21 | }, $list); 22 | 23 | return array_filter($_list); 24 | } 25 | 26 | public static function normalizePathFrag($path) 27 | { 28 | \Arg::_($path, 'Path')->is_string(); 29 | 30 | return trim(trim($path), '/'); 31 | } 32 | 33 | public static function includePatchwork() 34 | { 35 | if (function_exists('Patchwork\replace')) { 36 | return; 37 | } 38 | require_once Utils::getVendorDir('antecedent/patchwork/Patchwork.php'); 39 | } 40 | 41 | public static function findParentContainingFrom($children, $cwd) 42 | { 43 | $dir = $cwd; 44 | $children = '/' . self::normalizePathFrag($children); 45 | while (true) { 46 | if (file_exists($dir . $children)) { 47 | break; 48 | } else { 49 | $dir = dirname($dir); 50 | } 51 | } 52 | 53 | return $dir; 54 | } 55 | 56 | /** 57 | * Gets the absolute path to the `vendor` dir optionally appending a path. 58 | * 59 | * @param string $path The relative path with no leading slash. 60 | * 61 | * @return string The absolute path to the file. 62 | */ 63 | public static function getVendorDir($path = '') 64 | { 65 | $root = __DIR__; 66 | while (self::$vendorDir === null) { 67 | foreach (scandir($root, SCANDIR_SORT_ASCENDING) as $dir) { 68 | if (is_dir($root . '/' . implode(DIRECTORY_SEPARATOR, [$dir, 'antecedent', 'patchwork']))) { 69 | self::$vendorDir = realpath($root . '/' . $dir); 70 | break; 71 | } 72 | } 73 | $root = dirname($root); 74 | } 75 | 76 | return empty($path) ? self::$vendorDir : self::$vendorDir . '/'.$path; 77 | } 78 | } 79 | 80 | -------------------------------------------------------------------------------- /src/utils.php: -------------------------------------------------------------------------------- 1 | [dirname(__FILE__)], 6 | 'redefinable-internals' => ['array_reduce'] 7 | ]); 8 | 9 | foreach (glob(dirname(__FILE__) . '/test_supports/*.php') as $file) { 10 | include $file; 11 | } 12 | require_once 'classes.php'; 13 | 14 | -------------------------------------------------------------------------------- /tests/classes.php: -------------------------------------------------------------------------------- 1 | ns = __NAMESPACE__; 16 | } 17 | 18 | public function tearDown(): void { 19 | Test::tearDown(); 20 | } 21 | 22 | /** 23 | * @test 24 | * it should allow mocking an abstract class concrete instance method 25 | */ 26 | public function it_should_allow_mocking_an_abstract_class_concrete_instance_method() { 27 | $mock = Test::replace( $this->ns . '\SomeClass->methodOne', 23 ); 28 | 29 | $this->assertEquals( 23, $mock->methodOne() ); 30 | } 31 | 32 | /** 33 | * @test 34 | * it should allow mocking an abstract class abstract instance method 35 | */ 36 | public function it_should_allow_mocking_an_abstract_class_abstract_instance_method() { 37 | $mock = Test::replace( $this->ns . '\SomeClass::methodTwo', 23 ); 38 | 39 | $this->assertEquals( 23, $mock->methodTwo() ); 40 | } 41 | 42 | /** 43 | * @test 44 | * it should allow mocking an abstract class instance method and set a callback return value 45 | */ 46 | public function it_should_allow_mocking_an_abstract_class_instance_method_and_set_a_callback_return_value() { 47 | $mock = Test::replace( $this->ns . '\SomeClass::methodTwo', function () { 48 | return 23; 49 | } ); 50 | 51 | $this->assertEquals( 23, $mock->methodTwo() ); 52 | } 53 | 54 | /** 55 | * @test 56 | * it should allow replacing an abstract class instance method with a callback and pass arguments to it 57 | */ 58 | public function it_should_allow_replacing_an_abstract_class_instance_method_with_a_callback_and_pass_arguments_to_it() { 59 | $mock = Test::replace( $this->ns . '\SomeClass::methodThree', function ( $string, $int ) { 60 | return 23 + strlen( $string ) + $int; 61 | } ); 62 | 63 | $this->assertEquals( 28, $mock->methodThree( 'foo', 2 ) ); 64 | } 65 | 66 | /** 67 | * @test 68 | * it should allow replacing an abstract class abstract instance method with a callback and pass arguments to it 69 | */ 70 | public function it_should_allow_replacing_an_abstract_class_abstract_instance_method_with_a_callback_and_pass_arguments_to_it() { 71 | $mock = Test::replace( $this->ns . '\SomeClass::methodFour', function ( $string, $int ) { 72 | return 23 + strlen( $string ) + $int; 73 | } ); 74 | 75 | $this->assertEquals( 28, $mock->methodFour( 'foo', 2 ) ); 76 | } 77 | } 78 | 79 | 80 | abstract class SomeClass { 81 | 82 | public function methodOne() { 83 | } 84 | 85 | abstract public function methodTwo(); 86 | 87 | public function methodThree( $one, $two ) { 88 | } 89 | 90 | abstract public function methodFour( $one, $two ); 91 | } 92 | -------------------------------------------------------------------------------- /tests/tad/FunctionMocker/AliasedClassesTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 29 | } 30 | 31 | /** 32 | * @test 33 | * it should allow replacing a class method having an aliased type hinted parameter 34 | */ 35 | public function it_should_allow_replacing_a_class_method_having_an_aliased_type_hinted_parameter() { 36 | $class = 'Another\Acme\Class3'; 37 | 38 | FunctionMocker::replace( $class )->method( 'testMethod' )->get(); 39 | 40 | $this->assertTrue(true); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/tad/FunctionMocker/AssertionWrappingTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(23, Test::utilityMethod(11,12)); 39 | } 40 | 41 | public function aUtilityMethod($one, $two){ 42 | return $one + $two; 43 | } 44 | 45 | /** 46 | * @test 47 | * it should allow wrapping own test case utility method 48 | */ 49 | public function it_should_allow_wrapping_own_test_case_utility_method() 50 | { 51 | Test::setTestCase($this); 52 | 53 | $this->assertEquals(23, Test::aUtilityMethod(11,12)); 54 | } 55 | } 56 | 57 | class MockTestCase extends \PHPUnit\Framework\TestCase { 58 | public function utilityMethod($one, $two){ 59 | return $one + $two; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/tad/FunctionMocker/BatchReplaceTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('foo', $function()); 36 | } 37 | 38 | } 39 | 40 | /** 41 | * @test 42 | * it should allow batch replacing static methods 43 | */ 44 | public function it_should_allow_batch_replacing_static_methods() 45 | { 46 | $_functions = ['staticOne', 'staticTwo', 'staticThree']; 47 | $functions = array_map(function ($name) { 48 | return __NAMESPACE__ . '\\FooBazClass::' . $name; 49 | }, $_functions); 50 | 51 | FunctionMocker::replace($functions, 'foo'); 52 | 53 | $this->assertEquals('foo', FooBazClass::staticOne()); 54 | $this->assertEquals('foo', FooBazClass::staticTwo()); 55 | $this->assertEquals('foo', FooBazClass::staticThree()); 56 | } 57 | 58 | /** 59 | * @test 60 | * it should allow getting an array of replaced functions to spy 61 | */ 62 | public function it_should_allow_getting_an_array_of_replaced_functions_to_spy() 63 | { 64 | $_functions = ['functionOne', 'functionTwo', 'functionThree']; 65 | $functions = array_map(function ($name) { 66 | return __NAMESPACE__ . '\\' . $name; 67 | }, $_functions); 68 | 69 | $replacedFunctions = FunctionMocker::replace($functions, 'foo'); 70 | 71 | $this->assertInstanceOf('tad\FunctionMocker\Call\Verifier\FunctionCallVerifier', $replacedFunctions['functionOne']); 72 | $this->assertInstanceOf('tad\FunctionMocker\Call\Verifier\FunctionCallVerifier', $replacedFunctions['functionTwo']); 73 | $this->assertInstanceOf('tad\FunctionMocker\Call\Verifier\FunctionCallVerifier', $replacedFunctions['functionThree']); 74 | } 75 | 76 | /** 77 | * @test 78 | * it should return an array of replaced static methods to spy 79 | */ 80 | public function it_should_return_an_array_of_replaced_static_methods_to_spy() 81 | { 82 | $_functions = ['staticOne', 'staticTwo', 'staticThree']; 83 | $functions = array_map(function ($name) { 84 | return __NAMESPACE__ . '\\FooBazClass::' . $name; 85 | }, $_functions); 86 | 87 | $replacedFunctions = FunctionMocker::replace($functions, 'foo'); 88 | 89 | $this->assertInstanceOf('tad\FunctionMocker\Call\Verifier\StaticMethodCallVerifier', $replacedFunctions['staticOne']); 90 | $this->assertInstanceOf('tad\FunctionMocker\Call\Verifier\StaticMethodCallVerifier', $replacedFunctions['staticTwo']); 91 | $this->assertInstanceOf('tad\FunctionMocker\Call\Verifier\StaticMethodCallVerifier', $replacedFunctions['staticThree']); 92 | } 93 | 94 | /** 95 | * @test 96 | * it should allow getting a function name indexed list of functions when batch replacing 97 | */ 98 | public function it_should_allow_getting_a_function_name_indexed_list_of_functions_when_batch_replacing() 99 | { 100 | $_functions = ['functionOne', 'functionTwo', 'functionThree']; 101 | $functions = array_map(function ($name) { 102 | return __NAMESPACE__ . '\\' . $name; 103 | }, $_functions); 104 | 105 | $replacedFunctions = FunctionMocker::replace($functions, 'foo'); 106 | 107 | $this->assertInstanceOf('tad\FunctionMocker\Call\Verifier\FunctionCallVerifier', $replacedFunctions['functionOne']); 108 | $this->assertInstanceOf('tad\FunctionMocker\Call\Verifier\FunctionCallVerifier', $replacedFunctions['functionTwo']); 109 | $this->assertInstanceOf('tad\FunctionMocker\Call\Verifier\FunctionCallVerifier', $replacedFunctions['functionThree']); 110 | } 111 | } 112 | 113 | 114 | function functionOne() 115 | { 116 | return 1; 117 | } 118 | 119 | function functionTwo() 120 | { 121 | return 2; 122 | } 123 | 124 | function functionThree() 125 | { 126 | return 3; 127 | } 128 | 129 | 130 | class BazClass 131 | { 132 | 133 | function instanceOne() 134 | { 135 | 136 | } 137 | 138 | function instanceTwo() 139 | { 140 | 141 | } 142 | 143 | function instanceThree() 144 | { 145 | 146 | } 147 | } 148 | 149 | 150 | class FooBazClass 151 | { 152 | 153 | static function staticOne() 154 | { 155 | 156 | } 157 | 158 | static function staticTwo() 159 | { 160 | 161 | } 162 | 163 | static function staticThree() 164 | { 165 | 166 | } 167 | 168 | } 169 | -------------------------------------------------------------------------------- /tests/tad/FunctionMocker/Forge/StepTest.php: -------------------------------------------------------------------------------- 1 | class = $class = __NAMESPACE__ . '\StepDummyClass'; 14 | } 15 | 16 | /** 17 | * @test 18 | * it should throw if passing a non string arg 19 | */ 20 | public function it_should_throw_if_passing_a_non_string_arg() 21 | { 22 | $this->expectException('\Exception'); 23 | Step::instance(23); 24 | } 25 | 26 | /** 27 | * @test 28 | * it should throw if the class name is a non existing class 29 | */ 30 | public function it_should_throw_if_the_class_name_is_a_non_existing_class() 31 | { 32 | $this->expectException('\Exception'); 33 | Step::instance('SomeUnrealClass'); 34 | $this->assertTrue(true); 35 | } 36 | 37 | /** 38 | * @test 39 | * it should return a wrapped mock 40 | */ 41 | public function it_should_return_a_wrapped_mock() 42 | { 43 | $sut = new Step(); 44 | $sut->setClass($this->class); 45 | $this->set_instance_forger_on($sut); 46 | 47 | $mock = $sut->get(); 48 | 49 | $this->assertInstanceOf('tad\FunctionMocker\Call\Verifier\VerifierInterface', $mock); 50 | } 51 | 52 | /** 53 | * @param $sut 54 | */ 55 | private function set_instance_forger_on($sut) 56 | { 57 | $forger = new InstanceForger(); 58 | $forger->setTestCase($this); 59 | $sut->setInstanceForger($forger); 60 | $this->assertTrue(true); 61 | } 62 | 63 | /** 64 | * @test 65 | * it should allow defining methods to be replaced 66 | */ 67 | public function it_should_allow_defining_methods_to_be_replaced() 68 | { 69 | $sut = new Step($this->class); 70 | $sut->setClass($this->class); 71 | $this->set_instance_forger_on($sut); 72 | 73 | $sut->method('methodOne'); 74 | $mock = $sut->get(); 75 | 76 | $this->assertTrue(method_exists($mock, 'methodOne')); 77 | } 78 | 79 | /** 80 | * @test 81 | * it should be able to mock more than one method 82 | */ 83 | public function it_should_be_able_to_mock_more_than_one_method() 84 | { 85 | $sut = new Step($this->class); 86 | $sut->setClass($this->class); 87 | $this->set_instance_forger_on($sut); 88 | 89 | $sut->method('methodOne'); 90 | $sut->method('methodTwo'); 91 | $mock = $sut->get(); 92 | 93 | $this->assertTrue(method_exists($mock, 'methodOne')); 94 | $this->assertTrue(method_exists($mock, 'methodTwo')); 95 | } 96 | 97 | public function primitiveReturnValues() 98 | { 99 | $_values = [ 100 | 23, 101 | 'foo', 102 | new \stdClass(), 103 | array(), 104 | array('foo', 'baz'), 105 | array('some' => 'value', 'foo' => 23) 106 | ]; 107 | 108 | return array_map(function ($val) { 109 | return [$val]; 110 | }, $_values); 111 | } 112 | 113 | /** 114 | * @test 115 | * it should be able to replace methods and set primitive return value 116 | * @dataProvider primitiveReturnValues 117 | */ 118 | public function it_should_be_able_to_replace_methods_and_set_primitive_return_value($exp) 119 | { 120 | $sut = new Step($this->class); 121 | $sut->setClass($this->class); 122 | $this->set_instance_forger_on($sut); 123 | 124 | $sut->method('methodOne', $exp); 125 | $mock = $sut->get(); 126 | 127 | $this->assertEquals($exp, $mock->methodOne()); 128 | } 129 | 130 | /** 131 | * @test 132 | * it should allow setting the return values of replaced methods to callback functions 133 | */ 134 | public function it_should_allow_setting_the_return_values_of_replaced_methods_to_callback_functions() 135 | { 136 | $sut = new Step($this->class); 137 | $sut->setClass($this->class); 138 | $this->set_instance_forger_on($sut); 139 | $sut->method('methodOne', function () { 140 | return 23; 141 | }); 142 | $mock = $sut->get(); 143 | 144 | $this->assertEquals(23, $mock->methodOne()); 145 | } 146 | 147 | /** 148 | * @test 149 | * it should allow setting return values of replaced methods to callback functions and pass them same arguments as original methods 150 | */ 151 | public function it_should_allow_setting_return_values_of_replaced_methods_to_callback_functions_and_pass_them_same_arguments_as_original_methods() 152 | { 153 | $sut = new Step($this->class); 154 | $sut->setClass($this->class); 155 | $this->set_instance_forger_on($sut); 156 | $sut->method('methodThree', function ($one, $two) { 157 | return $one + $two; 158 | }); 159 | $mock = $sut->get(); 160 | 161 | $this->assertEquals(23, $mock->methodThree(1, 22)); 162 | } 163 | 164 | /** 165 | * @test 166 | * it should allow replacing self returning methods 167 | */ 168 | public function it_should_allow_replacing_self_returning_methods() 169 | { 170 | $sut = new Step($this->class); 171 | $sut->setClass($this->class); 172 | $this->set_instance_forger_on($sut); 173 | 174 | $mock = $sut->method('methodOne', '->') 175 | ->get(); 176 | 177 | $this->assertInstanceOf($this->class, $mock->methodOne()); 178 | } 179 | 180 | /** 181 | * @test 182 | * it should allow replace chain and value returning methods 183 | */ 184 | public function it_should_allow_replace_chain_and_value_returning_methods() 185 | { 186 | $sut = new Step($this->class); 187 | $sut->setClass($this->class); 188 | $this->set_instance_forger_on($sut); 189 | 190 | $mock = $sut->method('methodOne', '->') 191 | ->method('methodTwo', 23) 192 | ->get(); 193 | 194 | $this->assertEquals(23, $mock->methodOne()->methodTwo()); 195 | } 196 | 197 | /** 198 | * @test 199 | * it should allow verifying call times on methods 200 | */ 201 | public function it_should_allow_verifying_call_times_on_methods() 202 | { 203 | $sut = new Step($this->class); 204 | $sut->setClass($this->class); 205 | $this->set_instance_forger_on($sut); 206 | 207 | $mock = $sut->method('methodOne', '->') 208 | ->method('methodTwo', 23) 209 | ->get(); 210 | 211 | $mock->methodOne(); 212 | $mock->methodTwo(); 213 | 214 | $mock->wasCalledOnce('methodOne'); 215 | $mock->wasCalledOnce('methodTwo'); 216 | $mock->wasCalledWithTimes(['foo'], 0, 'methodOne'); 217 | 218 | $this->assertTrue(true); 219 | } 220 | 221 | /** 222 | * @test 223 | * it should create a Verifier object when calling the method verify 224 | */ 225 | public function it_should_create_a_verifier_object_when_calling_the_method_verify() 226 | { 227 | $sut = new Step($this->class); 228 | $sut->setClass($this->class); 229 | $this->set_instance_forger_on($sut); 230 | 231 | $verifier = $sut->verify(); 232 | 233 | $this->assertInstanceOf('tad\FunctionMocker\Call\Verifier\VerifierInterface', $verifier); 234 | } 235 | 236 | /** 237 | * @test 238 | * it should share the same original mock object 239 | */ 240 | public function it_should_share_the_same_original_mock_object() 241 | { 242 | $sut = new Step($this->class); 243 | $sut->setClass($this->class); 244 | $this->set_instance_forger_on($sut); 245 | 246 | $mock = $sut->get(); 247 | $verifier = $sut->verify(); 248 | 249 | $this->assertSame($mock->__functionMocker_originalMockObject, $verifier->__functionMocker_originalMockObject); 250 | } 251 | 252 | /** 253 | * @test 254 | * it should share the same invoked recorder 255 | */ 256 | public function it_should_share_the_same_invoked_recorder() 257 | { 258 | $sut = new Step($this->class); 259 | $sut->setClass($this->class); 260 | $this->set_instance_forger_on($sut); 261 | 262 | $mock = $sut->get(); 263 | $verifier = $sut->verify(); 264 | 265 | $this->assertSame($mock->__functionMocker_invokedRecorder, $verifier->__functionMocker_invokedRecorder); 266 | } 267 | 268 | /** 269 | * @test 270 | * it should allow skipping method name definition when using verify 271 | */ 272 | public function it_should_allow_skipping_method_name_definition_when_using_verify() 273 | { 274 | $sut = new Step($this->class); 275 | $sut->setClass($this->class); 276 | $this->set_instance_forger_on($sut); 277 | 278 | $mock = $sut->method('methodOne')->get(); 279 | $mock->methodOne(); 280 | 281 | $verifier = $sut->verify(); 282 | $verifier->methodOne()->wasCalledOnce(); 283 | $this->assertTrue(true); 284 | } 285 | 286 | /** 287 | * @test 288 | * it should allow verifying method was not called 289 | */ 290 | public function it_should_allow_verifying_method_was_not_called() 291 | { 292 | $sut = new Step($this->class); 293 | $sut->setClass($this->class); 294 | $this->set_instance_forger_on($sut); 295 | 296 | $mock = $sut->method('methodOne')->get(); 297 | 298 | $verifier = $sut->verify(); 299 | $verifier->methodOne()->wasNotCalled(); 300 | $this->assertTrue(true); 301 | } 302 | 303 | /** 304 | * @test 305 | * it should allow verifying method was called times 306 | */ 307 | public function it_should_allow_verifying_method_was_called_times() 308 | { 309 | $sut = new Step($this->class); 310 | $sut->setClass($this->class); 311 | $this->set_instance_forger_on($sut); 312 | 313 | $mock = $sut->method('methodOne')->get(); 314 | $mock->methodOne(); 315 | $mock->methodOne(); 316 | $mock->methodOne(); 317 | 318 | $verifier = $sut->verify(); 319 | $verifier->methodOne()->wasCalledTimes(3); 320 | $this->assertTrue(true); 321 | } 322 | 323 | /** 324 | * @test 325 | * it should allow verifying method was called with 326 | */ 327 | public function it_should_allow_verifying_method_was_called_with() 328 | { 329 | $sut = new Step($this->class); 330 | $sut->setClass($this->class); 331 | $this->set_instance_forger_on($sut); 332 | 333 | $mock = $sut->method('methodThree')->get(); 334 | $mock->methodThree('foo', 'bar'); 335 | 336 | $verifier = $sut->verify(); 337 | $verifier->methodThree('foo', 'bar')->wasCalledOnce(); 338 | $this->assertTrue(true); 339 | } 340 | 341 | /** 342 | * @test 343 | * it should allow verifying method was called times with 344 | */ 345 | public function it_should_allow_verifying_method_was_called_times_with() 346 | { 347 | $sut = new Step($this->class); 348 | $sut->setClass($this->class); 349 | $this->set_instance_forger_on($sut); 350 | 351 | $mock = $sut->method('methodThree')->get(); 352 | $mock->methodThree('foo', 'bar'); 353 | $mock->methodThree('foo', 'bar'); 354 | $mock->methodThree('foo', 'bar'); 355 | 356 | $verifier = $sut->verify(); 357 | $verifier->methodThree('foo', 'bar')->wasCalledTimes(3); 358 | $this->assertTrue(true); 359 | } 360 | 361 | /** 362 | * @test 363 | * it should return the same verifier instance on each call 364 | */ 365 | public function it_should_return_the_same_verifier_instance_on_each_call() 366 | { 367 | $sut = new Step($this->class); 368 | $sut->setClass($this->class); 369 | $this->set_instance_forger_on($sut); 370 | 371 | $verifyOne = $sut->verify(); 372 | $verifyTwo = $sut->verify(); 373 | 374 | $this->assertSame($verifyOne, $verifyTwo); 375 | } 376 | } 377 | 378 | 379 | class StepDummyClass 380 | { 381 | public function methodOne() 382 | { 383 | 384 | } 385 | 386 | public function methodTwo() 387 | { 388 | 389 | } 390 | 391 | public function methodThree($one, $two) 392 | { 393 | 394 | } 395 | } 396 | -------------------------------------------------------------------------------- /tests/tad/FunctionMocker/ForgeTest.php: -------------------------------------------------------------------------------- 1 | class = __NAMESPACE__ . '\ForgeClass'; 16 | } 17 | 18 | public function tearDown(): void 19 | { 20 | Sut::tearDown(); 21 | } 22 | 23 | /** 24 | * @test 25 | * it should allow calling the getMock method on a namespaced 26 | */ 27 | public function it_should_allow_calling_the_forge_method_on_a_namespaced_class() 28 | { 29 | Sut::forge($this->class); 30 | 31 | $this->assertTrue(true); 32 | } 33 | 34 | /** 35 | * @test 36 | * it should allow calling the getMock method on a global class 37 | */ 38 | public function it_should_allow_calling_the_forge_method_on_a_global_class() 39 | { 40 | Sut::forge('GlobalClass'); 41 | 42 | $this->assertTrue(true); 43 | } 44 | 45 | 46 | /** 47 | * @test 48 | * it should return ForgeStepInterface object 49 | */ 50 | public function it_should_return_forge_step_interface_object() 51 | { 52 | $this->assertInstanceOf('tad\FunctionMocker\Forge\StepInterface', Sut::forge('GlobalClass')); 53 | } 54 | } 55 | 56 | class ForgeClass 57 | { 58 | 59 | } 60 | } 61 | 62 | namespace { 63 | class GlobalClass 64 | { 65 | 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/tad/FunctionMocker/FunctionArgsConstraintsCheckTest.php: -------------------------------------------------------------------------------- 1 | expectFailure(); 40 | } 41 | $func = Sut::replace(__NAMESPACE__ . '\alpha'); 42 | 43 | alpha($callArg); 44 | 45 | $func->wasCalledWithOnce([$expectedArg]); 46 | } 47 | 48 | public function multipleCallArgsAndConstraints() 49 | { 50 | return [ 51 | [['foo', 'foo'], ['foo', 'foo'], true], 52 | [['foo', 'foo'], [Sut::isType('string'), 'foo'], true], 53 | [['foo', 'foo'], ['foo', Sut::isType('string')], true], 54 | [['foo', 'foo'], [Sut::isType('string'), Sut::isType('string')], true], 55 | [['foo', 'foo'], [Sut::isType('string'), 'baz'], false], 56 | [['foo', 'foo'], ['baz', Sut::isType('string')], false], 57 | [['foo', 'foo'], [Sut::isType('string'), Sut::isType('array')], false], 58 | [[new \stdClass(), 'foo'], [Sut::isInstanceOf('\stdClass'), Sut::isType('string')], true], 59 | [[new \stdClass(), 'foo'], [Sut::isInstanceOf('\stdClass'), 'foo'], true] 60 | ]; 61 | } 62 | 63 | /** 64 | * @test 65 | * it should allow verifying multiple arguments 66 | * @dataProvider multipleCallArgsAndConstraints 67 | */ 68 | public function it_should_allow_verifying_multiple_arguments($callArgs, $expectedArgs, $shouldPass) 69 | { 70 | if (!$shouldPass) { 71 | $this->expectFailure(); 72 | } 73 | $func = Sut::replace(__NAMESPACE__ . '\beta'); 74 | 75 | call_user_func_array(__NAMESPACE__ . '\beta', $callArgs); 76 | 77 | $func->wasCalledWithOnce($expectedArgs); 78 | } 79 | } 80 | 81 | function alpha($arg) 82 | { 83 | 84 | } 85 | 86 | function beta($arg1, $arg2) 87 | { 88 | 89 | } 90 | 91 | 92 | -------------------------------------------------------------------------------- /tests/tad/FunctionMocker/FunctionReplacementInOrderTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(function_exists($f)); 35 | foreach ( $values as $value ) { 36 | $this->assertEquals( $value, $f() ); 37 | } 38 | $spy->wasCalledTimes(count($values)); 39 | } 40 | 41 | /** 42 | * Test replace static methods should replace values in order 43 | * @dataProvider returnValues 44 | */ 45 | public function test_replace_static_methods_should_replace_values_in_order(array $values) { 46 | $f = 'tad\FunctionMocker\Tests\AClass::staticMethod'; 47 | $spy = FunctionMocker::replaceInOrder( $f, $values ); 48 | 49 | foreach ( $values as $value ) { 50 | $this->assertEquals( $value, $f() ); 51 | } 52 | $spy->wasCalledTimes(count($values)); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/tad/FunctionMocker/FunctionReplacementTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf('tad\FunctionMocker\Call\Verifier\VerifierInterface', $ret); 31 | } 32 | 33 | /** 34 | * @test 35 | * it should return the set return value when replacing a function and setting a return value 36 | */ 37 | public function it_should_return_the_set_return_value_when_replacing_a_function_and_setting_a_return_value() 38 | { 39 | $ret = FunctionMocker::replace(__NAMESPACE__ . '\someFunction', 23); 40 | 41 | $this->assertEquals(23, someFunction()); 42 | } 43 | 44 | /** 45 | * @test 46 | * it should return the callback return value when replacing a function and setting a callback return value 47 | */ 48 | public function it_should_return_the_callback_return_value_when_replacing_a_function_and_setting_a_callback_return_value() 49 | { 50 | $ret = FunctionMocker::replace(__NAMESPACE__ . '\someFunction', function ($value) { 51 | return $value + 1; 52 | }); 53 | 54 | $this->assertEquals(24, someFunction(23)); 55 | 56 | $ret = FunctionMocker::replace(__NAMESPACE__ . '\someFunction', function ($a, $b) { 57 | return $a + $b; 58 | }); 59 | 60 | $this->assertEquals(23, someFunction(11, 12)); 61 | } 62 | 63 | /** 64 | * @test 65 | * it should return null when replacing a function and not setting a return value 66 | */ 67 | public function it_should_return_null_when_replacing_a_function_and_not_setting_a_return_value() 68 | { 69 | $ret = FunctionMocker::replace(__NAMESPACE__ . '\someFunction'); 70 | 71 | $this->assertNull(someFunction()); 72 | $ret->wasCalledOnce(); 73 | $ret->wasCalledTimes(1); 74 | } 75 | 76 | /** 77 | * @test 78 | * it should allow setting various return values when spying a function 79 | */ 80 | public function it_should_allow_setting_various_return_values_when_spying_a_function() 81 | { 82 | $ret = FunctionMocker::replace(__NAMESPACE__ . '\someFunction', 23); 83 | 84 | $this->assertEquals(23, someFunction()); 85 | 86 | $ret = FunctionMocker::replace(__NAMESPACE__ . '\someFunction', function ($value) { 87 | return $value + 1; 88 | }); 89 | 90 | $this->assertEquals(24, someFunction(23)); 91 | 92 | $ret = FunctionMocker::replace(__NAMESPACE__ . '\someFunction', function ($a, $b) { 93 | return $a + $b; 94 | }); 95 | 96 | $this->assertEquals(23, someFunction(11, 12)); 97 | } 98 | 99 | /** 100 | * @test 101 | * it should allow verifying calls on spied function 102 | */ 103 | public function it_should_allow_verifying_calls_on_spied_function() 104 | { 105 | $spy = FunctionMocker::replace(__NAMESPACE__ . '\someFunction'); 106 | 107 | someFunction(12); 108 | someFunction(11); 109 | 110 | $spy->wasCalledTimes(2); 111 | $spy->wasCalledWithTimes(array(12), 1); 112 | $spy->wasCalledWithTimes(array(11), 1); 113 | $spy->wasNotCalledWith(array(10)); 114 | 115 | $this->expectFailure(); 116 | $spy->wasCalledTimes(0); 117 | } 118 | 119 | /** 120 | * It should allow replacing internal functions 121 | * @test 122 | */ 123 | public function allow_replacing_internal_functions() 124 | { 125 | FunctionMocker::replace('array_reduce', 2323); 126 | 127 | $this->assertEquals(2323, array_reduce([2, 3, 4, 5], function () { 128 | return 23; 129 | })); 130 | } 131 | 132 | /** 133 | * It should allow replacing a non defined non namespaced function 134 | * @test 135 | */ 136 | public function allow_replacing_a_non_defined_non_namespaced_function() 137 | { 138 | $f = 'func' . uniqid(rand(1, 9999)); 139 | 140 | $spy = FunctionMocker::replace($f, 2324); 141 | 142 | $this->assertTrue(function_exists($f)); 143 | $this->assertEquals(2324,$f()); 144 | $spy->wasCalledOnce(); 145 | } 146 | 147 | /** 148 | * It should allow replacing a non defined namespaced function 149 | * @test 150 | */ 151 | public function allow_replacing_a_non_defined_namespaced_function() 152 | { 153 | $f = 'Some\Name\Space\func' . uniqid(rand(1, 9999)); 154 | 155 | $spy = FunctionMocker::replace($f, 2324); 156 | 157 | $this->assertTrue(function_exists($f)); 158 | $this->assertEquals(2324, $f()); 159 | $spy->wasCalledOnce(); 160 | } 161 | 162 | /** 163 | * It should allow replacing a function result w/ an array 164 | * 165 | * @test 166 | */ 167 | public function should_allow_replacing_a_function_result_w_an_array() { 168 | $f = 'Some\Name\Space\func' . uniqid(rand(1, 9999)); 169 | 170 | $spy = FunctionMocker::replace($f, [23,89,2389]); 171 | 172 | $this->assertTrue(function_exists($f)); 173 | $this->assertEquals([23,89,2389], $f()); 174 | $spy->wasCalledOnce(); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /tests/tad/FunctionMocker/GlobalReplacementTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(23, $some->oneMethod()); 32 | $fooReplacement->wasCalledOnce('oneMethod'); 33 | } 34 | 35 | /** 36 | * @test 37 | * it should allow replacing an unset global variable 38 | */ 39 | public function it_should_allow_replacing_an_unset_global_variable() 40 | { 41 | $fooReplacement = Test::replaceGlobal('foo', __NAMESPACE__ . '\OneClass::oneMethod', 23); 42 | 43 | global $foo; 44 | $this->assertEquals(23, $foo->oneMethod()); 45 | $fooReplacement->wasCalledOnce('oneMethod'); 46 | } 47 | /** 48 | * @test 49 | * it should allow replacing a set global variable with a simple value 50 | */ 51 | public function it_should_allow_replacing_a_set_global_variable_with_a_simple_value() 52 | { 53 | $GLOBALS['foo'] = 200; 54 | $fooReplacement = Test::setGlobal('foo', 23); 55 | 56 | global $foo; 57 | $this->assertEquals(23, $foo); 58 | } 59 | 60 | /** 61 | * @test 62 | * it should backup and restore a global variable value at tear down 63 | */ 64 | public function it_should_backup_and_restore_a_global_variable_value_at_tear_down() 65 | { 66 | $GLOBALS['xyz'] = 200; 67 | Test::setGlobal('xyz', 23); 68 | 69 | global $xyz; 70 | $this->assertEquals(23, $xyz); 71 | 72 | Test::tearDown(); 73 | 74 | $this->assertEquals(200, $GLOBALS['xyz']); 75 | } 76 | } 77 | 78 | 79 | class OneClass 80 | { 81 | 82 | public function oneMethod() 83 | { 84 | return 200; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /tests/tad/FunctionMocker/InstanceMethodTest.php: -------------------------------------------------------------------------------- 1 | testClass = __NAMESPACE__ . '\TestClass'; 20 | FunctionMocker::setUp(); 21 | } 22 | 23 | public function tearDown(): void 24 | { 25 | FunctionMocker::tearDown(); 26 | } 27 | 28 | /** 29 | * @test 30 | * it should return an object extending the replaced one 31 | */ 32 | public function it_should_return_an_object_extending_the_replaced_one() 33 | { 34 | $stub = FunctionMocker::replace($this->testClass . '::methodOne'); 35 | 36 | $this->assertInstanceOf($this->testClass, $stub); 37 | } 38 | 39 | /** 40 | * @test 41 | * it should return an object implementing the 42 | * PHPUnit_Framework_MockObject_MockObject or 43 | * PHPUnit\Framework\MockObject\MockObject 44 | * interface when stubbing 45 | */ 46 | public function it_should_return_an_object_implementing_the_php_unit_framework_mock_object_mock_object_interface_when_stubbing() 47 | { 48 | $stub = FunctionMocker::replace($this->testClass . '::methodOne'); 49 | 50 | if ( 51 | class_exists(\PHPUnit_Runner_Version::class ) && 52 | version_compare( substr( \PHPUnit_Runner_Version::id(), 0, 1 ), '5', '=') 53 | ) { 54 | $this->assertInstanceOf('\PHPUnit_Framework_MockObject_MockObject', $stub); 55 | } else { 56 | $this->assertInstanceOf('\PHPUnit\Framework\MockObject\MockObject', $stub); 57 | } 58 | } 59 | 60 | /** 61 | * @test 62 | * it should return an object implementing the VerifierInterface interface when replacing 63 | */ 64 | public function it_should_return_an_object_implementing_the_verifier_interface_when_replacing() 65 | { 66 | $replacement = FunctionMocker::replace($this->testClass . '::methodOne'); 67 | 68 | $this->assertInstanceOf('tad\FunctionMocker\Call\Verifier\VerifierInterface', $replacement); 69 | } 70 | 71 | /** 72 | * @test 73 | * it should return null when replacing an instance method and not setting a return value 74 | */ 75 | public function it_should_return_null_when_replacing_an_instance_method_and_not_setting_a_return_value() 76 | { 77 | $replacement = FunctionMocker::replace($this->testClass . '::methodOne'); 78 | 79 | $this->assertNull($replacement->methodOne()); 80 | } 81 | 82 | public function returnValues() 83 | { 84 | return array( 85 | array(23), 86 | array('foo'), 87 | array(array()), 88 | array( 89 | array( 90 | 1, 91 | 2, 92 | 3 93 | ) 94 | ), 95 | array( 96 | array( 97 | 'one' => 1, 98 | 'two' => 2, 99 | 'three' => 3 100 | ) 101 | ), 102 | array(new \stdClass()), 103 | array(null) 104 | ); 105 | } 106 | 107 | /** 108 | * @test 109 | * it should return a set return value when replacing and setting a return value 110 | * @dataProvider returnValues 111 | */ 112 | public function it_should_return_a_set_return_value_when_replacing_and_setting_a_return_value($returnValue) 113 | { 114 | $replacement = FunctionMocker::replace($this->testClass . '::methodOne', $returnValue); 115 | 116 | $this->assertEquals($returnValue, $replacement->methodOne()); 117 | } 118 | 119 | /** 120 | * @test 121 | * it should return the return value of a callback function when replacing and setting the return value to a 122 | * callback function 123 | */ 124 | public function it_should_return_the_return_value_of_a_callback_function_when_replacing_and_setting_the_return_value_to_a_callback_function() 125 | { 126 | $returnValue = function () { 127 | return 'some'; 128 | }; 129 | $replacement = FunctionMocker::replace($this->testClass . '::methodOne', $returnValue); 130 | 131 | $this->assertEquals('some', $replacement->methodOne()); 132 | } 133 | 134 | /** 135 | * @test 136 | * it should return an object implementing the VerifierInterface interface when spying 137 | */ 138 | public function it_should_return_an_object_implementing_the_verifier_interface_when_spying() 139 | { 140 | $spy = FunctionMocker::replace($this->testClass . '::methodOne'); 141 | 142 | $this->assertInstanceOf('\tad\FunctionMocker\Call\Verifier\VerifierInterface', $spy); 143 | } 144 | 145 | /** 146 | * @test 147 | * it should return null if not setting a return value when spying an instance method 148 | */ 149 | public function it_should_return_null_if_not_setting_a_return_value_when_spying_an_instance_method() 150 | { 151 | $spy = FunctionMocker::replace($this->testClass . '::methodOne'); 152 | 153 | $this->assertNull($spy->methodOne()); 154 | } 155 | 156 | /** 157 | * @test 158 | * it should return a set return value when spying and setting a return value 159 | * @dataProvider returnValues 160 | */ 161 | public function it_should_return_a_set_return_value_when_spying_and_setting_a_return_value($returnValue) 162 | { 163 | $spy = FunctionMocker::replace($this->testClass . '::methodOne', $returnValue); 164 | 165 | $this->assertEquals($returnValue, $spy->methodOne()); 166 | } 167 | 168 | /** 169 | * @test 170 | * it should return the return value of a callback function when spying and setting a callback function as 171 | * return value 172 | */ 173 | public function it_should_return_the_return_value_of_a_callback_function_when_spying_and_setting_a_callback_function_as_return_value() 174 | { 175 | $returnValue = function () { 176 | return 'some'; 177 | }; 178 | $spy = FunctionMocker::replace($this->testClass . '::methodOne', $returnValue); 179 | 180 | $this->assertEquals('some', $spy->methodOne()); 181 | } 182 | 183 | public function timesCallsAndThrows() 184 | { 185 | return array( 186 | array( 187 | 2, 188 | 2, 189 | false 190 | ), 191 | array( 192 | 2, 193 | 1, 194 | true 195 | ), 196 | array( 197 | 2, 198 | 3, 199 | true 200 | ), 201 | array( 202 | '2', 203 | 2, 204 | false 205 | ), 206 | array( 207 | '>=2', 208 | 2, 209 | false 210 | ), 211 | array( 212 | '>=2', 213 | 3, 214 | false 215 | ), 216 | array( 217 | '>=2', 218 | 1, 219 | true 220 | ), 221 | array( 222 | '<=2', 223 | 2, 224 | false 225 | ), 226 | array( 227 | '<=2', 228 | 3, 229 | true 230 | ), 231 | array( 232 | '<=2', 233 | 1, 234 | false 235 | ), 236 | array( 237 | '<2', 238 | 2, 239 | true 240 | ), 241 | array( 242 | '<2', 243 | 1, 244 | false 245 | ), 246 | array( 247 | '<2', 248 | 4, 249 | true 250 | ), 251 | array( 252 | '>2', 253 | 2, 254 | true 255 | ), 256 | array( 257 | '>2', 258 | 1, 259 | true 260 | ), 261 | array( 262 | '>2', 263 | 3, 264 | false 265 | ), 266 | array( 267 | '!=2', 268 | 2, 269 | true 270 | ), 271 | array( 272 | '!=2', 273 | 1, 274 | false 275 | ), 276 | array( 277 | '!=2', 278 | 3, 279 | false 280 | ), 281 | array( 282 | '==2', 283 | 3, 284 | true 285 | ), 286 | array( 287 | '==2', 288 | 2, 289 | false 290 | ), 291 | array( 292 | '==2', 293 | 1, 294 | true 295 | ) 296 | ); 297 | } 298 | 299 | /** 300 | * @test 301 | * it should allow verifying an instance method was called times when spying 302 | * @dataProvider timesCallsAndThrows 303 | */ 304 | public function it_should_allow_verifying_an_instance_method_was_called_times_when_spying($times, $calls, $shouldThrow) 305 | { 306 | if ($shouldThrow) { 307 | $this->expectFailure(); 308 | } 309 | $spy = FunctionMocker::replace($this->testClass . '::methodOne'); 310 | 311 | for ($i = 0; $i < $calls; $i++) { 312 | $spy->methodOne(); 313 | } 314 | 315 | $spy->wasCalledTimes($times, 'methodOne'); 316 | } 317 | 318 | /** 319 | * @test 320 | * it should allow verifying an instance method was called times with args when spying 321 | * @dataProvider timesCallsAndThrows 322 | */ 323 | public function it_should_allow_verifying_an_instance_method_was_called_times_with_args_when_spying($times, $calls, $shouldThrow) 324 | { 325 | if ($shouldThrow) { 326 | $this->expectFailure(); 327 | } 328 | $spy = FunctionMocker::replace($this->testClass . '::methodTwo'); 329 | 330 | for ($i = 0; $i < $calls; $i++) { 331 | $spy->methodTwo(23, 23); 332 | } 333 | 334 | $spy->wasCalledWithTimes(array( 335 | 23, 336 | 23 337 | ), $times, 'methodTwo'); 338 | } 339 | 340 | /** 341 | * @test 342 | * it should allow calling the replacement inside a closure 343 | */ 344 | public function it_should_allow_calling_the_replacement_inside_a_closure() 345 | { 346 | $methodOne = FunctionMocker::replace($this->testClass . '::methodOne'); 347 | 348 | $caller = function (TestClass $testClass) { 349 | $testClass->methodOne(); 350 | }; 351 | 352 | $caller($methodOne); 353 | 354 | $methodOne->wasCalledTimes(1, 'methodOne'); 355 | } 356 | 357 | /** 358 | * @test 359 | * it should allow replacing a concrete class instance method with a callback and pass arguments to it 360 | */ 361 | public function it_should_allow_replacing_a_concrete_class_instance_method_with_a_callback_and_pass_arguments_to_it() 362 | { 363 | $testClass = FunctionMocker::replace($this->testClass . '::methodTwo', function ($one, $two) { 364 | return $one + $two; 365 | }); 366 | 367 | $this->assertEquals(4, $testClass->methodTwo(1, 3)); 368 | } 369 | 370 | public function callsAndConstraints() 371 | { 372 | return [ 373 | [ 374 | [new \stdClass(), [1, 2, 3], 'foo'], 375 | [ 376 | FunctionMocker::isInstanceOf('stdClass'), 377 | FunctionMocker::isType('array'), 378 | FunctionMocker::isType('string') 379 | ], 380 | false 381 | ], 382 | [ 383 | [null, [1, 2, 3], 'foo'], 384 | [ 385 | FunctionMocker::isInstanceOf('stdClass'), 386 | FunctionMocker::isType('array'), 387 | FunctionMocker::isType('string') 388 | ], 389 | true 390 | ], 391 | [ 392 | [new \stdClass(), null, 'foo'], 393 | [ 394 | FunctionMocker::isInstanceOf('stdClass'), 395 | FunctionMocker::isType('array'), 396 | FunctionMocker::isType('string') 397 | ], 398 | true 399 | ], 400 | [ 401 | [new \stdClass(), [1, 2, 3], null], 402 | [ 403 | FunctionMocker::isInstanceOf('stdClass'), 404 | FunctionMocker::isType('array'), 405 | FunctionMocker::isType('string') 406 | ], 407 | true 408 | ], 409 | [ 410 | ['foo', [], null], 411 | [ 412 | FunctionMocker::isInstanceOf('stdClass'), 413 | FunctionMocker::isType('array'), 414 | FunctionMocker::isType('string') 415 | ], 416 | true 417 | ], 418 | [ 419 | ['foo', [], null], 420 | [ 421 | FunctionMocker::anything(), 422 | FunctionMocker::anything(), 423 | FunctionMocker::anything() 424 | ], 425 | false 426 | ] 427 | ]; 428 | } 429 | 430 | /** 431 | * @test 432 | * it should allow verifying method calls using PHPUnit constraints 433 | * @dataProvider callsAndConstraints 434 | */ 435 | public function it_should_allow_verifying_method_calls_using_php_unit_constraints($in, $exp, $shouldThrow) 436 | { 437 | if ($shouldThrow) { 438 | $this->expectFailure(); 439 | } 440 | 441 | $sut = FunctionMocker::replace($this->testClass . '::methodThree'); 442 | 443 | call_user_func_array([$sut, 'methodThree'], $in); 444 | 445 | $sut->wasCalledWithOnce($exp, 'methodThree'); 446 | } 447 | 448 | /** 449 | * @test 450 | * it should allow start an instance method replacement chain trying to replace a class name only 451 | */ 452 | public function it_should_allow_start_an_instance_method_replacement_chain_trying_to_replace_a_class_name_only() 453 | { 454 | $sut = FunctionMocker::replace($this->testClass); 455 | 456 | $this->assertInstanceOf('tad\FunctionMocker\Forge\Step', $sut); 457 | } 458 | } 459 | 460 | 461 | class TestClass 462 | { 463 | 464 | public function methodOne() 465 | { 466 | 467 | } 468 | 469 | public function methodTwo($one, $two) 470 | { 471 | 472 | } 473 | 474 | public function methodThree($object, $array, $string) 475 | { 476 | 477 | } 478 | } 479 | 480 | -------------------------------------------------------------------------------- /tests/tad/FunctionMocker/InterfaceMockingTest.php: -------------------------------------------------------------------------------- 1 | ns = __NAMESPACE__; 24 | } 25 | 26 | public function tearDown(): void { 27 | Test::tearDown(); 28 | } 29 | 30 | /** 31 | * @test 32 | * it should allow mocking an interface instance method 33 | */ 34 | public function it_should_allow_mocking_an_interface_instance_method() { 35 | $mock = Test::replace( __NAMESPACE__ . '\SomeI::methodOne', 23 ); 36 | 37 | $this->assertEquals( 23, $mock->methodOne() ); 38 | } 39 | 40 | /** 41 | * @test 42 | * it should allow mocking an interface instance method and return a callback 43 | */ 44 | public function it_should_allow_mocking_an_interface_instance_method_and_return_a_callback() { 45 | $mock = Test::replace( __NAMESPACE__ . '\SomeI::methodOne', function () { 46 | return 23; 47 | } ); 48 | 49 | $this->assertEquals( 23, $mock->methodOne() ); 50 | } 51 | 52 | /** 53 | * @test 54 | * it should allow mocking an interface instance method and pass arguments to it 55 | */ 56 | public function it_should_allow_mocking_an_interface_instance_method_and_pass_arguments_to_it() { 57 | $mock = Test::replace( __NAMESPACE__ . '\SomeI::methodWithArgs', function ( $string, $int ) { 58 | return 23 + strlen( $string ) + $int; 59 | } ); 60 | 61 | $this->assertEquals( 28, $mock->methodWithArgs( 'foo', 2 ) ); 62 | } 63 | 64 | /** 65 | * @test 66 | * it should allow mocking an interface using the chain 67 | */ 68 | public function it_should_allow_mocking_an_interface_using_the_chain() { 69 | $mock = Test::replace( __NAMESPACE__ . '\SomeI' )->get(); 70 | 71 | $this->assertInstanceOf( __NAMESPACE__ . '\SomeI', $mock ); 72 | } 73 | 74 | /** 75 | * @test 76 | * it should return a mock step object when mocking using the chain 77 | */ 78 | public function it_should_return_a_mock() { 79 | $step = Test::replace( __NAMESPACE__ . '\SomeI' ); 80 | 81 | $class = 'tad\FunctionMocker\Forge\Step'; 82 | $this->assertInstanceOf( $class, $step ); 83 | } 84 | 85 | /** 86 | * @test 87 | * it should allow mocking an interface method using the chain 88 | */ 89 | public function it_should_allow_mocking_an_interface_method_using_the_chain() { 90 | $mock = Test::replace( __NAMESPACE__ . '\SomeI' )->method( 'methodOne', 'foo' )->get(); 91 | 92 | $this->assertEquals( 'foo', $mock->methodOne() ); 93 | } 94 | 95 | /** 96 | * @test 97 | * it should allow mocking an interface method and make it return null using the chain 98 | */ 99 | public function it_should_allow_mocking_an_interface_method_and_make_it_return_null_using_the_chain() { 100 | $mock = Test::replace( __NAMESPACE__ . '\SomeI' )->method( 'methodOne' )->get(); 101 | 102 | $this->assertNull( $mock->methodOne() ); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /tests/tad/FunctionMocker/PassAndCallOriginalTest.php: -------------------------------------------------------------------------------- 1 | assertEquals( 'foo', passingFunction() ); 35 | } 36 | 37 | /** 38 | * @test 39 | * it should allow calling the original function with args 40 | */ 41 | public function it_should_allow_calling_the_original_function_with_args() { 42 | FunctionMocker::replace( __NAMESPACE__ . '\passingFunctionWithArgs', function ( $one, $two ) { 43 | return FunctionMocker::callOriginal( [ $one, $two ] ); 44 | } ); 45 | 46 | $this->assertEquals( 'foo and baz', passingFunctionWithArgs( 'foo', 'baz' ) ); 47 | } 48 | 49 | /** 50 | * @test 51 | * it should allow calling original method and have the original return value 52 | */ 53 | public function it_should_allow_calling_original_method_and_have_the_original_return_value() { 54 | FunctionMocker::replace( __NAMESPACE__ . '\PassingClass::passingStaticMethod', function () { 55 | return FunctionMocker::callOriginal(); 56 | } ); 57 | 58 | $this->assertEquals( 'foo', PassingClass::passingStaticMethod() ); 59 | } 60 | 61 | /** 62 | * @test 63 | * it should allow calling the original method with args 64 | */ 65 | public function it_should_allow_calling_the_original_method_with_args() { 66 | FunctionMocker::replace( __NAMESPACE__ . '\PassingClass::passingStaticMethodWithArgs', function ( $one, $two ) { 67 | return FunctionMocker::callOriginal( [ $one, $two ] ); 68 | } ); 69 | 70 | $this->assertEquals( 'foo and baz', PassingClass::passingStaticMethodWithArgs( 'foo', 'baz' ) ); 71 | } 72 | 73 | /** 74 | * @test 75 | * it should allow setting up conditional calls to the original function 76 | */ 77 | public function it_should_allow_setting_up_conditional_calls_to_the_original_function() { 78 | FunctionMocker::replace( __NAMESPACE__ . '\passingFunctionWithArgs', function ( $one, $two ) { 79 | if ( $one === 'some' ) { 80 | return 'test'; 81 | } 82 | 83 | return FunctionMocker::callOriginal( [ $one, $two ] ); 84 | } ); 85 | 86 | $this->assertEquals( 'foo and baz', passingFunctionWithArgs( 'foo', 'baz' ) ); 87 | $this->assertEquals( 'test', passingFunctionWithArgs( 'some', 'baz' ) ); 88 | } 89 | 90 | /** 91 | * @test 92 | * it should allow setting up conditional calls to the original static method 93 | */ 94 | public function it_should_allow_setting_up_conditional_calls_to_the_original_static_method() { 95 | FunctionMocker::replace( __NAMESPACE__ . '\PassingClass::passingStaticMethodWithArgs', function ( $one, $two ) { 96 | if ( $one === 'some' ) { 97 | return 'test'; 98 | } 99 | 100 | return FunctionMocker::callOriginal( [ $one, $two ] ); 101 | } ); 102 | 103 | $this->assertEquals( 'foo and baz', PassingClass::passingStaticMethodWithArgs( 'foo', 'baz' ) ); 104 | $this->assertEquals( 'test', PassingClass::passingStaticMethodWithArgs( 'some', 'baz' ) ); 105 | } 106 | } 107 | 108 | 109 | function passingFunction() { 110 | return 'foo'; 111 | } 112 | 113 | function passingFunctionWithArgs( $one, $two ) { 114 | return $one . ' and ' . $two; 115 | } 116 | 117 | 118 | class PassingClass { 119 | 120 | public static function passingStaticMethod() { 121 | return 'foo'; 122 | } 123 | 124 | public static function passingStaticMethodWithArgs( $one, $two ) { 125 | return $one . ' and ' . $two; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /tests/tad/FunctionMocker/PatchworkConfigurationTest.php: -------------------------------------------------------------------------------- 1 | removeFiles(); 17 | } 18 | 19 | protected function tearDown(): void { 20 | $this->removeFiles(); 21 | } 22 | 23 | /** 24 | * It should write to specified destination 25 | * 26 | * @test 27 | */ 28 | public function should_write_to_specified_destination() { 29 | $configContents = ['whitelist' => __DIR__]; 30 | $encoded = json_encode(FunctionMocker::getPatchworkConfiguration($configContents, __DIR__)); 31 | $checksum = md5($encoded); 32 | $configFile = __DIR__ . '/patchwork.json'; 33 | $checksumFile = __DIR__ . "/pw-cs-{$checksum}.yml"; 34 | $this->toRemove[] = $configFile; 35 | $this->toRemove[] = $checksumFile; 36 | 37 | $this->assertTrue(FunctionMocker::writePatchworkConfig($configContents, __DIR__)); 38 | 39 | $this->assertFileExists($configFile); 40 | $this->assertFileExists($checksumFile); 41 | $this->assertJsonStringEqualsJsonFile($configFile, $encoded); 42 | } 43 | 44 | /** 45 | * It should not write the configuration file again if written and matching 46 | * 47 | * @test 48 | */ 49 | public function should_not_write_the_configuration_file_again_if_written_and_matching() { 50 | $configContents = ['whitelist' => __DIR__]; 51 | $encoded = json_encode(FunctionMocker::getPatchworkConfiguration($configContents, __DIR__)); 52 | $checksum = md5($encoded); 53 | $configFile = __DIR__ . '/patchwork.json'; 54 | $checksumFile = __DIR__ . "/pw-cs-{$checksum}.yml"; 55 | $this->toRemove[] = $configFile; 56 | $this->toRemove[] = $checksumFile; 57 | file_put_contents($configFile, 'foo'); 58 | file_put_contents($checksumFile, 'bar'); 59 | 60 | $this->assertFalse(FunctionMocker::writePatchworkConfig($configContents, __DIR__)); 61 | 62 | $this->assertFileExists($configFile); 63 | $this->assertFileExists($checksumFile); 64 | $this->assertStringEqualsFile($configFile, 'foo'); 65 | } 66 | 67 | /** 68 | * It should rewrite configuration file if changed 69 | * 70 | * @test 71 | */ 72 | public function should_rewrite_configuration_file_if_changed() { 73 | $previousConfigContents = ['blacklist' => __DIR__]; 74 | $encoded = json_encode(FunctionMocker::getPatchworkConfiguration($previousConfigContents, __DIR__)); 75 | $previousChecksum = md5($encoded); 76 | $configFile = __DIR__ . '/patchwork.json'; 77 | $previousChecksumFile = __DIR__ . "/pw-cs-{$previousChecksum}.yml"; 78 | $this->toRemove[] = $configFile; 79 | $this->toRemove[] = $previousChecksumFile; 80 | file_put_contents($configFile, $encoded); 81 | file_put_contents($previousChecksumFile, 'bar'); 82 | 83 | $newConfigContents = ['whitelist' => [__DIR__, dirname(__DIR__)], 'cache-path' => dirname(__DIR__)]; 84 | $this->assertTrue(FunctionMocker::writePatchworkConfig($newConfigContents, __DIR__)); 85 | 86 | $this->assertFileExists($configFile); 87 | if ( 88 | class_exists( Version::class ) && 89 | version_compare( substr( Version::id(), 0, 1 ), '9', '>=') 90 | ) { 91 | $this->assertFileDoesNotExist($previousChecksumFile); 92 | } else { 93 | $this->assertFileNotExists($previousChecksumFile); 94 | } 95 | $newChecksum = md5(json_encode(FunctionMocker::getPatchworkConfiguration($newConfigContents, __DIR__))); 96 | $newChecksumFile = __DIR__ . "/pw-cs-{$newChecksum}.yml"; 97 | $this->assertFileExists($newChecksumFile); 98 | } 99 | 100 | protected function removeFiles() { 101 | foreach ($this->toRemove as $item) { 102 | if (!file_exists($item)) { 103 | continue; 104 | } 105 | if (is_dir($item)) { 106 | `rmdir -rf $item`; 107 | } 108 | unlink($item); 109 | } 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /tests/tad/FunctionMocker/StaticMethodConstraintsCheckTest.php: -------------------------------------------------------------------------------- 1 | expectFailure(); 47 | } 48 | 49 | $sut = Sut::replace(__NAMESPACE__ . '\ConstraintClass::methodOne'); 50 | 51 | call_user_func_array([__NAMESPACE__ . '\ConstraintClass', 'methodOne'], $in); 52 | 53 | $sut->wasCalledWithOnce($exp); 54 | } 55 | 56 | public function callsAndConstraints() 57 | { 58 | return [ 59 | [['foo', 'foo'], [Sut::isType('string'), 'foo'], true], 60 | [['foo', 'foo'], ['baz', Sut::isType('string')], false], 61 | [['foo', 'foo'], [Sut::isType('array'), Sut::isType('array')], false], 62 | [[1, 2], [Sut::isType('int'), Sut::isType('int')], true], 63 | [[1, 2], [Sut::isType('string'), Sut::isType('int')], false], 64 | [[1, 2], [Sut::isType('string'), Sut::isType('string')], false], 65 | [[array(1, 2), array(3, 4)], [Sut::isType('array'), array(3, 4)], true], 66 | [[array(1, 2), array(3, 4)], [Sut::isType('array'), Sut::isType('array')], true], 67 | [[new \stdClass(), array(3, 4)], [Sut::isInstanceOf('\stdClass'), Sut::isType('array')], true], 68 | [[new \stdClass(), array(3, 4)], [Sut::isInstanceOf('\stdClass'), array(3, 4)], true], 69 | [[new \stdClass(), array(3, 4)], [Sut::anything(), Sut::anything()], true] 70 | ]; 71 | } 72 | 73 | /** 74 | * @test 75 | * it should allow to check method call args using PHPUnit constraints 76 | * @dataProvider callsAndConstraints 77 | */ 78 | public function it_should_allow_to_check_method_call_args_using_php_unit_constraints($in, $exp, $shouldPass) 79 | { 80 | if (!$shouldPass) { 81 | $this->expectFailure(); 82 | } 83 | 84 | $sut = Sut::replace(__NAMESPACE__ . '\ConstraintClass::methodOne'); 85 | 86 | call_user_func_array([__NAMESPACE__ . '\ConstraintClass', 'methodOne'], $in); 87 | 88 | $sut->wasCalledWithOnce($exp); 89 | } 90 | 91 | } 92 | 93 | class ConstraintClass 94 | { 95 | public static function methodOne($arg1, $arg2) 96 | { 97 | 98 | } 99 | } -------------------------------------------------------------------------------- /tests/tad/FunctionMocker/StaticMethodTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf( 'tad\FunctionMocker\Call\Verifier\VerifierInterface', $ret ); 27 | } 28 | 29 | /** 30 | * @test 31 | * it should allow setting various return values when replacing a static method 32 | */ 33 | public function it_should_allow_setting_various_return_values_when_replacing_a_static_method() { 34 | $ret = FunctionMocker::replace( __NAMESPACE__ . '\SomeClass::staticMethod', 23 ); 35 | 36 | $this->assertEquals( 23, SomeClass::staticMethod() ); 37 | 38 | $ret = FunctionMocker::replace( __NAMESPACE__ . '\SomeClass::staticMethod', function ( $a ) { 39 | return $a + 1; 40 | } ); 41 | 42 | $this->assertEquals( 24, SomeClass::staticMethod( 23 ) ); 43 | 44 | $ret = FunctionMocker::replace( __NAMESPACE__ . '\SomeClass::staticMethod', function ( $a, $b ) { 45 | return $a + $b; 46 | } ); 47 | 48 | $this->assertEquals( 24, SomeClass::staticMethod( 23, 1 ) ); 49 | } 50 | 51 | /** 52 | * @test 53 | * it should return a matcher when spying a static method 54 | */ 55 | public function it_should_return_a_matcher_when_spying_a_static_method() { 56 | $ret = FunctionMocker::replace( __NAMESPACE__ . '\SomeClass::staticMethod' ); 57 | 58 | $this->assertInstanceOf( 'tad\FunctionMocker\Call\Verifier\FunctionCallVerifier', $ret ); 59 | } 60 | 61 | /** 62 | * @test 63 | * it should return null when replacing a static method and not setting a return value 64 | */ 65 | public function it_should_retur_null_when_replacing_a_static_method_and_not_setting_a_return_value() { 66 | $ret = FunctionMocker::replace( __NAMESPACE__ . '\SomeClass::staticMethod' ); 67 | 68 | $this->assertNull( SomeClass::staticMethod() ); 69 | $ret->wasCalledOnce(); 70 | $ret->wasCalledTimes( 1 ); 71 | } 72 | 73 | /** 74 | * @test 75 | * it should allow verifying calls on spied static method 76 | */ 77 | public function it_should_allow_verifying_calls_on_spied_static_method() { 78 | $spy = FunctionMocker::replace( __NAMESPACE__ . '\SomeClass::staticMethod' ); 79 | 80 | SomeClass::staticMethod( 12 ); 81 | SomeClass::staticMethod( 11 ); 82 | 83 | $spy->wasCalledTimes( 2 ); 84 | $spy->wasCalledWithTimes( array( 12 ), 1 ); 85 | $spy->wasCalledWithTimes( array( 11 ), 1 ); 86 | $spy->wasNotCalledWith( array( 10 ) ); 87 | 88 | $this->expectFailure(); 89 | $spy->wasCalledTimes( 0 ); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /tests/tad/FunctionMocker/TestWrappingTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(23 ,FunctionMocker::someMethod()); 23 | } 24 | 25 | public function someMethod(){ 26 | return 23; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/tad/FunctionMocker/TraitMockingTest.php: -------------------------------------------------------------------------------- 1 | sutClass = __NAMESPACE__ . '\TestTrait'; 42 | Test::setUp(); 43 | } 44 | 45 | public function tearDown(): void { 46 | Test::tearDown(); 47 | } 48 | 49 | public function returnValues() { 50 | return [ 51 | [ 23, 23 ], [ 52 | function () { 53 | return 23; 54 | }, 23 55 | ] 56 | ]; 57 | 58 | } 59 | 60 | /** 61 | * @test 62 | * it should replacing a trait instance method 63 | * @dataProvider returnValues 64 | */ 65 | public function it_should_replacing_a_trait_instance_method( $in, $out ) { 66 | $mock = Test::replace( $this->sutClass . '::methodOne', $in ); 67 | 68 | $this->assertEquals( $out, $mock->methodOne() ); 69 | } 70 | 71 | /** 72 | * @test 73 | * it should allow replacing a trait instance method with a callback and pass arguments to it 74 | */ 75 | public function it_should_allow_replacing_a_trait_instance_method_with_a_callback_and_pass_arguments_to_it() { 76 | $mock = Test::replace( $this->sutClass . '::methodTwo', function ( $one, $two ) { 77 | return $one + $two; 78 | } ); 79 | 80 | $this->assertEquals( 23, $mock->methodTwo( 11, 12 ) ); 81 | } 82 | 83 | } -------------------------------------------------------------------------------- /tests/tad/FunctionMocker/UtilsTest.php: -------------------------------------------------------------------------------- 1 | rootDir = dirname( dirname( dirname( dirname( __FILE__ ) ) ) ); 20 | } 21 | 22 | public function pathArrays() { 23 | $rootDir = dirname( dirname( dirname( dirname( __FILE__ ) ) ) ); 24 | 25 | return [ 26 | [ [ 'vendor/some' ], [ ] ], 27 | [ [ 'vendor/bin' ], [ $rootDir . '/vendor/bin' ] ], 28 | [ [ 'vendor/bin', 'vendor/some' ], [ $rootDir . '/vendor/bin' ] ] 29 | ]; 30 | } 31 | 32 | /** 33 | * @test 34 | * it should return properly filtered paths arrays 35 | * @dataProvider pathArrays 36 | */ 37 | public function it_should_return_properly_filtered_paths_arrays( $in, $out ) { 38 | $in = str_replace( '/', DIRECTORY_SEPARATOR, $in ); 39 | $out = str_replace( '/', DIRECTORY_SEPARATOR, $out ); 40 | 41 | $this->assertEquals( $out, Utils::filterPathListFrom( $in, $this->rootDir ) ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/tad/ReplacementRequestTest.php: -------------------------------------------------------------------------------- 1 | expectException('InvalidArgumentException'); 17 | ReplacementRequest::on(23); 18 | } 19 | 20 | /** 21 | * @test 22 | * it should allow doing function requests 23 | */ 24 | public function it_should_allow_doing_function_requests() 25 | { 26 | $sut = ReplacementRequest::on('someFunction'); 27 | 28 | $this->assertTrue($sut->isFunction()); 29 | $this->assertFalse($sut->isInstanceMethod()); 30 | $this->assertFalse($sut->isMethod()); 31 | $this->assertFalse($sut->isStaticMethod()); 32 | } 33 | 34 | 35 | /** 36 | * @test 37 | * it should allow doing static method requests 38 | */ 39 | public function it_should_allow_doing_static_method_requests() 40 | { 41 | $sut = ReplacementRequest::on(__NAMESPACE__ . '\RequestClassOne::methodOne'); 42 | 43 | $this->assertTrue($sut->isMethod()); 44 | $this->assertTrue($sut->isStaticMethod()); 45 | $this->assertFalse($sut->isFunction()); 46 | $this->assertFalse($sut->isInstanceMethod()); 47 | } 48 | 49 | /** 50 | * @test 51 | * it should allow doing instance method requests 52 | */ 53 | public function it_should_allow_doing_instance_method_requests() 54 | { 55 | $sut = ReplacementRequest::on(__NAMESPACE__ . '\RequestClassOne::methodTwo'); 56 | 57 | $this->assertTrue($sut->isMethod()); 58 | $this->assertTrue($sut->isInstanceMethod()); 59 | $this->assertFalse($sut->isStaticMethod()); 60 | $this->assertFalse($sut->isFunction()); 61 | } 62 | 63 | /** 64 | * @test 65 | * it should allow doing instance methods requests using arrow symbol 66 | */ 67 | public function it_should_allow_doing_instance_methods_requests_using_arrow_symbol() 68 | { 69 | $sut = ReplacementRequest::on(__NAMESPACE__ . '\RequestClassOne->methodTwo'); 70 | 71 | $this->assertTrue($sut->isMethod()); 72 | $this->assertTrue($sut->isInstanceMethod()); 73 | $this->assertFalse($sut->isStaticMethod()); 74 | $this->assertFalse($sut->isFunction()); 75 | } 76 | 77 | /** 78 | * @test 79 | * it should throw if trying to make static method requests using the arrow symbol 80 | */ 81 | public function it_should_throw_if_trying_to_make_static_method_requests_using_the_arrow_symbol() 82 | { 83 | $this->expectException('InvalidArgumentException'); 84 | $sut = ReplacementRequest::on(__NAMESPACE__ . '\RequestClassOne->methodOne'); 85 | } 86 | 87 | /** 88 | * @test 89 | * it should return proper function name 90 | */ 91 | public function it_should_return_proper_function_name() 92 | { 93 | $sut = ReplacementRequest::on('someFunction'); 94 | $this->assertEquals('someFunction', $sut->getMethodName()); 95 | } 96 | 97 | /** 98 | * @test 99 | * it should return proper function name for namespaced functions 100 | */ 101 | public function it_should_return_proper_function_name_for_namespaced_functions() 102 | { 103 | $functionName = __NAMESPACE__ . '\someFunction'; 104 | $sut = ReplacementRequest::on($functionName); 105 | $this->assertEquals($functionName, $sut->getMethodName()); 106 | } 107 | 108 | /** 109 | * @test 110 | * it should return proper class and method name for static method replacement requests 111 | */ 112 | public function it_should_return_proper_class_and_method_name_for_static_method_replacement_requests() 113 | { 114 | $sut = ReplacementRequest::on(__NAMESPACE__ . '\RequestClassOne::methodOne'); 115 | $this->assertEquals(__NAMESPACE__ . '\RequestClassOne', $sut->getClassName()); 116 | $this->assertEquals('methodOne', $sut->getMethodName()); 117 | } 118 | 119 | /** 120 | * @test 121 | * it should return proper clas and method name for instance method replacement requests 122 | */ 123 | public function it_should_return_proper_clas_and_method_name_for_instance_method_replacement_requests() 124 | { 125 | $sut = ReplacementRequest::on(__NAMESPACE__ . '\RequestClassOne->methodTwo'); 126 | $this->assertEquals(__NAMESPACE__ . '\RequestClassOne', $sut->getClassName()); 127 | $this->assertEquals('methodTwo', $sut->getMethodName()); 128 | } 129 | } 130 | 131 | class RequestClassOne 132 | { 133 | public static function methodOne() 134 | { 135 | 136 | } 137 | 138 | public function methodTwo() 139 | { 140 | 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /tests/test_supports/Class1.php: -------------------------------------------------------------------------------- 1 | expectException(\PHPUnit_Framework_AssertionFailedError::class); 11 | } 12 | } 13 | } else { 14 | class TestCase extends \PHPUnit\Framework\TestCase 15 | { 16 | protected function expectFailure() 17 | { 18 | $this->expectException(\PHPUnit\Framework\AssertionFailedError::class); 19 | } 20 | } 21 | } --------------------------------------------------------------------------------