├── .github └── workflows │ ├── coding-standards.yml │ ├── phpmd.yml │ ├── test-flight.yml │ └── tests.yml ├── .gitignore ├── LICENSE ├── README.md ├── classes ├── MockDelegateFunction.tpl └── MockDelegateFunctionBuilder.php ├── composer.json ├── phpunit.xml └── tests ├── MockDelegateFunctionBuilderTest.php ├── MockDelegateFunctionTest.php ├── TestCaseNoTypeHintTrait.php ├── TestCaseTypeHintTrait.php └── autoload.php /.github/workflows/coding-standards.yml: -------------------------------------------------------------------------------- 1 | name: "Check Coding Standards" 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | coding-standards: 8 | name: "Check Coding Standards" 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | 16 | - name: Setup PHP 17 | uses: shivammathur/setup-php@v2 18 | with: 19 | php-version: "7.4" 20 | tools: composer:v2, cs2pr 21 | 22 | - name: Install dependencies 23 | run: composer require squizlabs/php_codesniffer --no-interaction --no-progress 24 | 25 | - name: "Run phpcs" 26 | run: vendor/bin/phpcs -q --standard=PSR2 classes/ tests/ --report=checkstyle | cs2pr 27 | -------------------------------------------------------------------------------- /.github/workflows/phpmd.yml: -------------------------------------------------------------------------------- 1 | name: "Check phpmd" 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | coding-standards: 8 | name: "Check phpmd" 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | 16 | - name: Setup PHP 17 | uses: shivammathur/setup-php@v2 18 | with: 19 | php-version: "7.4" 20 | tools: composer:v2 21 | 22 | - name: Install dependencies 23 | run: composer require phpmd/phpmd --no-interaction --no-progress 24 | 25 | - name: Run phpmd 26 | run: vendor/bin/phpmd classes/ text cleancode,codesize,controversial,design,naming,unusedcode 27 | -------------------------------------------------------------------------------- /.github/workflows/test-flight.yml: -------------------------------------------------------------------------------- 1 | name: "Check test-flight" 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | coding-standards: 8 | name: "Check test-flight" 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | 16 | - name: Setup PHP 17 | uses: shivammathur/setup-php@v2 18 | with: 19 | php-version: "7.4" 20 | tools: composer:v2 21 | ini-values: "zend.assertions=1" 22 | 23 | - name: Install dependencies 24 | run: composer require cundd/test-flight --no-interaction --no-progress 25 | 26 | - name: Run test-flight 27 | run: | 28 | vendor/bin/test-flight README.md 29 | vendor/bin/test-flight classes/ 30 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | schedule: 4 | - cron: "0 0 * * *" 5 | 6 | concurrency: 7 | group: ${{ github.head_ref || 'cron' }} 8 | cancel-in-progress: true 9 | 10 | jobs: 11 | tests: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | php-version: 16 | - '8.4' 17 | - '8.3' 18 | - '8.2' 19 | - '8.1' 20 | - '8.0' 21 | - '7.4' 22 | - '7.3' 23 | - '7.2' 24 | - '7.1' 25 | - '7.0' 26 | - '5.6' 27 | 28 | name: PHP ${{ matrix.php-version }} 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v4 32 | 33 | - name: Install PHP 34 | uses: shivammathur/setup-php@v2 35 | with: 36 | php-version: ${{ matrix.php-version }} 37 | 38 | - name: Install Dependencies 39 | run: composer install --no-interaction --no-progress 40 | 41 | - name: PHPUnit 42 | run: vendor/bin/phpunit 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.phpunit.result.cache 2 | /composer.lock 3 | /vendor/ 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![.github/workflows/tests.yml](https://github.com/php-mock/php-mock-integration/actions/workflows/tests.yml/badge.svg)](https://github.com/php-mock/php-mock-integration/actions/workflows/tests.yml) 2 | 3 | # PHP-Mock integration package 4 | 5 | This is a support package for PHP-Mock integration into other frameworks. 6 | You'll find these integrations: 7 | 8 | - [php-mock/php-mock-phpunit](https://github.com/php-mock/php-mock-phpunit) - PHPUnit integration 9 | 10 | - [php-mock/php-mock-mockery](https://github.com/php-mock/php-mock-mockery) - Mockery integration 11 | 12 | - [php-mock/php-mock-prophecy](https://github.com/php-mock/php-mock-prophecy) - Prophecy (phpspec) integration 13 | 14 | ## License and authors 15 | 16 | This project is free and under the WTFPL. 17 | Responsable for this project is Markus Malkusch markus@malkusch.de. 18 | 19 | ## Donations 20 | 21 | If you like PHP-Mock and feel generous donate a few Bitcoins here: 22 | [1335STSwu9hST4vcMRppEPgENMHD2r1REK](bitcoin:1335STSwu9hST4vcMRppEPgENMHD2r1REK) 23 | -------------------------------------------------------------------------------- /classes/MockDelegateFunction.tpl: -------------------------------------------------------------------------------- 1 | namespace {namespace}; 2 | 3 | use phpmock\functions\FunctionProvider; 4 | 5 | /** 6 | * Function provider which delegates to a mockable MockDelegate. 7 | * 8 | * @author Markus Malkusch 9 | * @link bitcoin:1335STSwu9hST4vcMRppEPgENMHD2r1REK Donations 10 | * @license http://www.wtfpl.net/txt/copying/ WTFPL 11 | * @internal 12 | */ 13 | class MockDelegateFunction implements FunctionProvider 14 | { 15 | 16 | /** 17 | * A mocked function will redirect its call to this method. 18 | * 19 | * @return mixed Returns the function output. 20 | */ 21 | public function delegate({signatureParameters}) 22 | { 23 | } 24 | 25 | public function getCallable() 26 | { 27 | return [$this, "delegate"]; 28 | } 29 | 30 | {function} 31 | } 32 | -------------------------------------------------------------------------------- /classes/MockDelegateFunctionBuilder.php: -------------------------------------------------------------------------------- 1 | 12 | * @link bitcoin:1335STSwu9hST4vcMRppEPgENMHD2r1REK Donations 13 | * @license http://www.wtfpl.net/txt/copying/ WTFPL 14 | * @internal 15 | */ 16 | class MockDelegateFunctionBuilder 17 | { 18 | 19 | /** 20 | * The delegation method name. 21 | */ 22 | const METHOD = "delegate"; 23 | 24 | /** 25 | * @var string The namespace of the build class. 26 | */ 27 | private $namespace; 28 | 29 | /** 30 | * @var Template The MockDelegateFunction template. 31 | */ 32 | private $template; 33 | 34 | /** 35 | * Instantiation. 36 | */ 37 | public function __construct() 38 | { 39 | $this->template = new Template(__DIR__ . '/MockDelegateFunction.tpl'); 40 | } 41 | 42 | /** 43 | * Builds a MockDelegateFunction for a function. 44 | * 45 | * @param string|null $functionName The mocked function. 46 | * 47 | * @SuppressWarnings(PHPMD) 48 | */ 49 | public function build($functionName = null) 50 | { 51 | $functionNameOrEmptyString = $functionName === null ? '' : $functionName; 52 | 53 | $parameterBuilder = new ParameterBuilder(); 54 | $parameterBuilder->build($functionNameOrEmptyString); 55 | $signatureParameters = $parameterBuilder->getSignatureParameters(); 56 | 57 | /** 58 | * If a class with the same signature exists, it is considered equivalent 59 | * to the generated class. 60 | */ 61 | $hash = md5($functionNameOrEmptyString . $signatureParameters); 62 | $this->namespace = __NAMESPACE__ . $hash; 63 | if (class_exists($this->getFullyQualifiedClassName())) { 64 | return; 65 | } 66 | 67 | $data = [ 68 | "namespace" => $this->namespace, 69 | "signatureParameters" => $signatureParameters, 70 | "function" => $functionName === null ? '' : 'public function ' . $functionName . '() {}', 71 | ]; 72 | $this->template->setVar($data, false); 73 | $definition = $this->template->render(); 74 | 75 | eval($definition); 76 | } 77 | 78 | /** 79 | * Returns the fully qualified class name 80 | * 81 | * @return string The class name. 82 | */ 83 | public function getFullyQualifiedClassName() 84 | { 85 | return "$this->namespace\\MockDelegateFunction"; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "php-mock/php-mock-integration", 3 | "type": "library", 4 | "description": "Integration package for PHP-Mock", 5 | "keywords": ["mock", "stub", "test double", "function", "test", "TDD", "BDD"], 6 | "homepage": "https://github.com/php-mock/php-mock-integration", 7 | "license": "WTFPL", 8 | "authors": [ 9 | { 10 | "name": "Markus Malkusch", 11 | "email": "markus@malkusch.de", 12 | "homepage": "http://markus.malkusch.de", 13 | "role": "Developer" 14 | } 15 | ], 16 | "autoload": { 17 | "psr-4": {"phpmock\\integration\\": "classes/"} 18 | }, 19 | "autoload-dev": { 20 | "files": ["tests/autoload.php"], 21 | "psr-4": {"phpmock\\integration\\": "tests/"} 22 | }, 23 | "require": { 24 | "php": ">=5.6", 25 | "php-mock/php-mock": "^2.5", 26 | "phpunit/php-text-template": "^1 || ^2 || ^3 || ^4 || ^5" 27 | }, 28 | "require-dev": { 29 | "phpunit/phpunit": "^5.7.27 || ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12" 30 | }, 31 | "archive": { 32 | "exclude": ["/tests"] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | tests/ 7 | 8 | -------------------------------------------------------------------------------- /tests/MockDelegateFunctionBuilderTest.php: -------------------------------------------------------------------------------- 1 | 11 | * @link bitcoin:1335STSwu9hST4vcMRppEPgENMHD2r1REK Donations 12 | * @license http://www.wtfpl.net/txt/copying/ WTFPL 13 | * @see MockDelegateFunctionBuilder 14 | */ 15 | class MockDelegateFunctionBuilderTest extends TestCase 16 | { 17 | 18 | /** 19 | * Test build() defines a class. 20 | */ 21 | public function testBuild() 22 | { 23 | $builder = new MockDelegateFunctionBuilder(); 24 | $builder->build(); 25 | $this->assertTrue(class_exists($builder->getFullyQualifiedClassName())); 26 | } 27 | 28 | /** 29 | * Test build() would never create the same class name for different signatures. 30 | */ 31 | public function testDiverseSignaturesProduceDifferentClasses() 32 | { 33 | $builder = new MockDelegateFunctionBuilder(); 34 | 35 | $builder->build('f0'); 36 | $class1 = $builder->getFullyQualifiedClassName(); 37 | 38 | $builder->build('f1'); 39 | $class2 = $builder->getFullyQualifiedClassName(); 40 | 41 | $builder2 = new MockDelegateFunctionBuilder(); 42 | $builder2->build('f2'); 43 | $class3 = $builder2->getFullyQualifiedClassName(); 44 | 45 | $this->assertNotEquals($class1, $class2); 46 | $this->assertNotEquals($class1, $class3); 47 | $this->assertNotEquals($class2, $class3); 48 | } 49 | 50 | /** 51 | * Test build() would create the same class name for identical signatures. 52 | */ 53 | public function testSameSignaturesProduceSameClass() 54 | { 55 | $builder = new MockDelegateFunctionBuilder(); 56 | 57 | $builder->build('f1'); 58 | $class1 = $builder->getFullyQualifiedClassName(); 59 | 60 | $builder->build('f1'); 61 | $class2 = $builder->getFullyQualifiedClassName(); 62 | 63 | $this->assertEquals($class1, $class2); 64 | } 65 | 66 | /** 67 | * Tests declaring a class with enabled backupStaticAttributes. 68 | * 69 | * @backupStaticAttributes enabled 70 | * @dataProvider provideTestBackupStaticAttributes 71 | * 72 | * @doesNotPerformAssertions 73 | */ 74 | #[\PHPUnit\Framework\Attributes\DoesNotPerformAssertions] 75 | #[\PHPUnit\Framework\Attributes\DataProvider('provideTestBackupStaticAttributes')] 76 | #[\PHPUnit\Framework\Attributes\BackupStaticProperties(true)] 77 | public function testBackupStaticAttributes() 78 | { 79 | $builder = new MockDelegateFunctionBuilder(); 80 | $builder->build("min"); 81 | } 82 | 83 | /** 84 | * Just repeat testBackupStaticAttributes a few times. 85 | * 86 | * @return array Test cases. 87 | */ 88 | public static function provideTestBackupStaticAttributes() 89 | { 90 | return [ 91 | [], 92 | [] 93 | ]; 94 | } 95 | 96 | /** 97 | * Tests deserialization. 98 | * 99 | * @runInSeparateProcess 100 | * 101 | * @doesNotPerformAssertions 102 | */ 103 | #[\PHPUnit\Framework\Attributes\RunInSeparateProcess] 104 | #[\PHPUnit\Framework\Attributes\DoesNotPerformAssertions] 105 | public function testDeserializationInNewProcess() 106 | { 107 | $builder = new MockDelegateFunctionBuilder(); 108 | $builder->build("min"); 109 | 110 | $data = serialize($this->getMockBuilder($builder->getFullyQualifiedClassName())->getMock()); 111 | 112 | unserialize($data); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /tests/MockDelegateFunctionTest.php: -------------------------------------------------------------------------------- 1 | 11 | * @link bitcoin:1335STSwu9hST4vcMRppEPgENMHD2r1REK Donations 12 | * @license http://www.wtfpl.net/txt/copying/ WTFPL 13 | * @see MockDelegateFunction 14 | */ 15 | class MockDelegateFunctionTest extends TestCase 16 | { 17 | use TestCaseTrait; 18 | 19 | /** 20 | * @var string The class name of a generated class. 21 | */ 22 | private $className; 23 | 24 | protected function setUpCompat() 25 | { 26 | parent::setUp(); 27 | 28 | $builder = new MockDelegateFunctionBuilder(); 29 | $builder->build(); 30 | $this->className = $builder->getFullyQualifiedClassName(); 31 | } 32 | 33 | /** 34 | * Tests delegate() returns the mock's result. 35 | * 36 | * @test 37 | */ 38 | #[\PHPUnit\Framework\Attributes\Test] 39 | public function testDelegateReturnsMockResult() 40 | { 41 | $expected = 3; 42 | $mockBuilder = $this->getMockBuilder($this->className); 43 | 44 | // `setMethods` is gone from phpunit 10, alternative is `onlyMethods` 45 | if (method_exists($mockBuilder, 'onlyMethods')) { 46 | $mockBuilder->onlyMethods([MockDelegateFunctionBuilder::METHOD]); 47 | } else { 48 | $mockBuilder->setMethods([MockDelegateFunctionBuilder::METHOD]); 49 | } 50 | 51 | $mock = $mockBuilder->getMock(); 52 | 53 | $mock->expects($this->once()) 54 | ->method(MockDelegateFunctionBuilder::METHOD) 55 | ->willReturn($expected); 56 | 57 | $result = call_user_func($mock->getCallable()); 58 | $this->assertEquals($expected, $result); 59 | } 60 | 61 | /** 62 | * Tests delegate() forwards the arguments. 63 | * 64 | * @test 65 | */ 66 | #[\PHPUnit\Framework\Attributes\Test] 67 | public function testDelegateForwardsArguments() 68 | { 69 | $mockBuilder = $this->getMockBuilder($this->className); 70 | 71 | // `setMethods` is gone from phpunit 10, alternative is `onlyMethods` 72 | if (method_exists($mockBuilder, 'onlyMethods')) { 73 | $mockBuilder->onlyMethods([MockDelegateFunctionBuilder::METHOD]); 74 | } else { 75 | $mockBuilder->setMethods([MockDelegateFunctionBuilder::METHOD]); 76 | } 77 | 78 | $mock = $mockBuilder->getMock(); 79 | 80 | $mock->expects($this->once()) 81 | ->method(MockDelegateFunctionBuilder::METHOD) 82 | ->with(1, 2); 83 | 84 | call_user_func($mock->getCallable(), 1, 2); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /tests/TestCaseNoTypeHintTrait.php: -------------------------------------------------------------------------------- 1 | setUpCompat(); 14 | } 15 | } 16 | 17 | protected function tearDown() 18 | { 19 | if (method_exists($this, 'tearDownCompat')) { 20 | $this->tearDownCompat(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/TestCaseTypeHintTrait.php: -------------------------------------------------------------------------------- 1 | setUpCompat(); 14 | } 15 | } 16 | 17 | protected function tearDown(): void 18 | { 19 | if (method_exists($this, 'tearDownCompat')) { 20 | $this->tearDownCompat(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/autoload.php: -------------------------------------------------------------------------------- 1 | = 0 11 | ) { 12 | class_alias(\phpmock\integration\TestCaseTypeHintTrait::class, \phpmock\integration\TestCaseTrait::class); 13 | } else { 14 | class_alias(\phpmock\integration\TestCaseNoTypeHintTrait::class, \phpmock\integration\TestCaseTrait::class); 15 | } 16 | 17 | function f0() 18 | { 19 | } 20 | function f1($a) 21 | { 22 | } 23 | function f2($a, $b) 24 | { 25 | } 26 | --------------------------------------------------------------------------------