From 78b264017bb4bbb5f2077184d0ecfec8a06ab3d8 Mon Sep 17 00:00:00 2001 From: Luca Tumedei Date: Tue, 12 Mar 2024 16:35:05 +0100 Subject: [PATCH] feat(Traits/UopzFunctions) add trait and documentation --- docs/traits/UopzFunctions.md | 1305 +++++++++++++++++ mkdocs.yml | 6 + src/Traits/UopzFunctions.php | 504 +++++++ tests/_data/uopz-test/functions.php | 69 + tests/_data/uopz-test/global-classes.php | 115 ++ tests/_data/uopz-test/namespaced-classes.php | 105 ++ tests/_support/Traits/UopzFunctions.php | 202 --- .../WPBrowser/Command/DevInfoTest.php | 8 +- .../WPBrowser/Command/DevStartTest.php | 8 +- .../WPBrowser/Command/DevStopTest.php | 8 +- .../lucatume/WPBrowser/Command/RunAllTest.php | 8 +- .../WPBrowser/Events/DispatcherTest.php | 2 +- .../WPBrowser/Events/Module/WPDbTest.php | 7 +- .../WPBrowser/Events/Module/WPQueriesTest.php | 3 +- .../Extension/BuiltInServerControllerTest.php | 8 +- .../Extension/ChromeDriverControllerTest.php | 6 +- .../Extension/DockerComposeControllerTest.php | 28 +- .../Extension/EventDispatcherBridgeTest.php | 10 +- .../ManagedProcess/ChromedriverTest.php | 10 +- .../ManagedProcess/PhpBuiltInServerTest.php | 6 +- .../Process/Protocol/ControlTest.php | 6 +- .../Process/Protocol/ResponseTest.php | 7 +- .../WPBrowser/Project/PluginProjectTest.php | 2 +- .../WPBrowser/Project/ThemeProjectTest.php | 2 +- .../WPBrowser/Traits/UopzFunctionsTest.php | 1253 ++++++++++++++++ .../Utils/ChromedriverInstallerTest.php | 36 +- .../lucatume/WPBrowser/Utils/ComposerTest.php | 16 +- .../WordPress/Database/MysqlDatabaseTest.php | 7 +- .../WordPress/Database/SqliteDatabaseTest.php | 12 +- .../InstallationState/ConfiguredTest.php | 2 +- .../InstallationState/EmptyDirTest.php | 2 +- .../InstallationState/MultisiteTest.php | 2 +- .../InstallationState/ScaffoldedTest.php | 6 +- .../InstallationState/SingleTest.php | 8 +- .../WPBrowser/WordPress/InstallationTest.php | 7 +- .../WordPress/WpConfigFileGeneratorTest.php | 4 +- tests/wploadersuite/AjaxTest.php | 2 +- 37 files changed, 3470 insertions(+), 322 deletions(-) create mode 100644 docs/traits/UopzFunctions.md create mode 100644 src/Traits/UopzFunctions.php create mode 100644 tests/_data/uopz-test/functions.php create mode 100644 tests/_data/uopz-test/global-classes.php create mode 100644 tests/_data/uopz-test/namespaced-classes.php delete mode 100644 tests/_support/Traits/UopzFunctions.php create mode 100644 tests/unit/lucatume/WPBrowser/Traits/UopzFunctionsTest.php diff --git a/docs/traits/UopzFunctions.md b/docs/traits/UopzFunctions.md new file mode 100644 index 000000000..14e85ab40 --- /dev/null +++ b/docs/traits/UopzFunctions.md @@ -0,0 +1,1305 @@ +This trait provides a set of methods to manipulate functions, methods and class attributes using the [uopz][1] PHP +extension. + +!!! warning + + This test trait requires the `uopz` PHP extension. + + See the [Installing the extension locally](#installing-the-extension-locally) section of this page for more information about how to do that. + If you need to install the extension in a CI environment, see the [Installing the extension in CI](#installing-the-extension-in-ci) section of this page. + + If the `uopz` extension is not installed, test methods using methods from the `UopzFunctions` trait will be marked as skipped. + +### Why require an extension? + +Why use a PHP extension instead of a user-land solution, i.e. a PHP library that does not require installing an +extension? + +I've written such a solution myself, [function-mocker][3], but have grown frustrated with its limitations, and the +limitation of other similar solutions. + +All user-land, monkey-patching, pure PHP solutions rely on [stream-wrapping][4]. +This is a very powerful feature that this project uses for some of its functionality, but it has a drawbacks when used +extensively for monkey-patching functions and methods: + +* the files patch must be included **after** the library loaded +* the files have to patched, or patched and cached, on each run +* there are some random and difficult to track issues introduced by how function and method patching works; e.g. + functions manipulating values by reference will not work as expected +* some constants like `__METHOD__` and `__FUNCTION__` will not work as expected in the patched files +* monkey-patching code will be "inserted" in the function stack, lengthening the stack trace and making it very + difficult to debug +* all this processing together with [XDebug][9] spells doom for the performance of the test suite + +The `uopz` extension is a **solid and fast** solution that has been created and maintained by people that know PHP +internals and the PHP language very well that has **none of the drawbacks** of the above-mentioned solutions. + +It is just a better tool for the job. + +### Installing the extension locally + +=== "Windows" + + * Locate your `php.ini` file: + ```powershell + php --ini + ``` + * Download the latest `DLL` stable version of the extension from the [releases page][2]. You'll likely need the `NTS x64` version. + * Unzip the file and copy the `php_uopz.dll` file to the `ext` folder of your PHP installation. If your `php.ini` file is located at `C:\tools\php81\php.ini`, the extensions directory will be located at `C:\tools\php81\ext`. + * Edit your `php.ini` file and add the following line to enable and configure the extension: + ```ini + extension=uopz + uopz.exit=1 + ``` + * Make sure the extension is correctly installed by running `php -m` and making sure the `uopz` extension appears in the list of extensions. + + You can find more information about installing PHP extensions on Windows in the [PHP manual][6] and in [the `uopz` extension install guide][7]. + +=== "Linux" + + * Use the `pecl` command to install the extension: + ```bash + pecl install uopz + ``` + * Configure the extension to ensure it will allow `exit` and `die` calls to terminate the script execution. + Add the following line to either the main PHP configuration file (`php.ini`), or a dedicated configuration file: + ```ini + uopz.exit=1 + ``` + * Make sure the extension is correctly installed by running `php -m` and making sure the `uopz` extension appears in the list of extensions. + + Alternatively, you can build the extension from source as detailed in [the `uopz` extension install guide][7]. + +=== "MacOS" + + * Use the `pecl` command to install the extension: + ```bash + pecl install uopz + ``` + * Configure the extension to ensure it will allow `exit` and `die` calls to terminate the script execution. + Add the following line to either the main PHP configuration file (`php.ini`), or a dedicated configuration file: + ```ini + uopz.exit=1 + ``` + * Make sure the extension is correctly installed by running `php -m` and making sure the `uopz` extension appears in the list of extensions. + + Alternatively, you can build the extension from source as detailed in [the `uopz` extension install guide][7]. + +### Installing the extension in CI + +Depending on your Continuous Integration (CI) solution of choice, the configuration required to install and set up +the `uopz` extensions will be different. + +As an example, here is how you can set up the `uopz` extension in a GitHub Actions job: + +```yaml +- name: Setup PHP 8.1 with uopz +uses: shivammathur/setup-php@v2 +with: + php-version: 8.1 + extensions: uopz + ini-values: uopz.exit=1 +``` + +[This project uses the very same setup][8]. + +Most CI systems are based on Linux OSes: if you're not using GitHub Actions, you can reference to the +Linux [local installation instructions](#installing-the-extension-locally) to set up and install the extension for your +CI solution of choice. + +### Usage + +Include the `UopzFunctions` trait in your test class and use the methods provided by the trait to manipulate functions, +methods and class attributes. + +```php +setFunctionReturn('wp_create_nonce', 'super-secret-nonce'); + + $this->assertEquals('super-secret-nonce', wp_create_nonce('some-action')); + } +} +``` + +The trait will take care of cleaning up all the modifications made to the functions, methods and class attributes after +each test. + +You can use the `UopzFunctions` trait in test cases extending the `PHUnit\Framework\TestCase` class as well: + +```php +setFunctionReturn('someFunction', 'mocked-value'); + + $this->assertEquals('mocked-value', someFunction()); + } +} +``` + +### Methods + +The `UopzFunctions` trait provides the following methods: + +#### setFunctionReturn + +`setFunctionReturn(string $function, mixed $value, bool $execute = false): void` + +Set the return value for the function `$function` to `$value`. + +If `$value` is a closure and `$execute` is `true`, then the return value will be the return value of the closure. + +```php +setFunctionReturn('wp_generate_nonce', 'super-secret-nonce'); + + $this->assertEquals('super-secret-nonce', wp_create_nonce('some-action')); + } +} +``` + +If `$value` is a closure, the original function can be called within the closure to relay the original return value: + +```php +setFunctionReturn( + 'wp_generate_nonce', + fn(string $action) => $action === 'test' ? 'test-nonce' : wp_create_nonce($action), + true + ); + + $this->assertEquals('test-nonce', wp_create_nonce('test')); + $this->assertNotEquals('test-nonce', wp_create_nonce('some-other-action')); + } +} +``` + +#### unsetFunctionReturn + +`unsetFunctionReturn(string $function): void` + +Unset the return value for the function `$function` previously set with [`setFunctionReturn`](#setfunctionreturn). + +You do not need to unset the return value for a function that was set with [`setFunctionReturn`](#setfunctionreturn) +using `unsetFunctionReturn` explicitly: the trait will take care of cleaning up all the modifications made to the +functions, methods and class attributes after each test. + +#### setMethodReturn + +`setMethodReturn(string $class, string $method, mixed $value, bool $execute = false): void` + +Sets the return value for the static or instance method `$method` of the class `$class` to `$value`. + +If `$value` is a closure and `$execute` is `true`, then the return value will be the return value of the closure. + +Magic methods like `__construct`, `__destruct`, `__call` and so on cannot be mocked using this method. +See the [`setClassMock`](#setclassmock) method for more information about how to mock magic class methods. + +```php +setMethodReturn(SomeLegacyClass::class, 'staticMethod', 'STATIC'); + $this->setMethodReturn(SomeLegacyClass::class, 'instanceMethod', 'TEST'); + + $legacyClass = new SomeLegacyClass(); + + $this->assertEquals('STATIC', SomeLegacyClass::staticMethod()); + $this->assertEquals('TEST', $legacyClass->instanceMethod()); + } +} +``` + +If `$value` is a closure, the original static or instance method can be called within the closure, with correctly +bound `self` and `$this` context, to relay the original return value: + +```php +setMethodReturn( + SomeLegacyClass::class, + 'raiseStaticFlag', + fn(bool $flag) => $flag ? 'STATIC' : self::raiseStaticFlag($flag), + true + ); + $this->setMethodReturn( + SomeLegacyClass::class, + 'raiseFlag', + fn(bool $flag) => $flag ? 'TEST' : $this->raiseFlag($flag), + true + ); + + $legacyClass = new SomeLegacyClass(); + + $this->assertEquals('STATIC', SomeLegacyClass::raiseStaticFlag(true)); + $this->assertEquals('static-flag-lowered', SomeLegacyClass::raiseStaticFlag(false)); + $this->assertEquals('TEST', $legacyClass->raiseFlag(true)); + $this->assertEquals('flag-lowered', $legacyClass->raiseFlag(false)); + } +} +``` + +#### unsetmethodreturn + +`unsetmethodreturn(string $class, string $method): void` + +Unset the return value for the static or instance method `$method` of the class `$class` previously set +with [`setMethodReturn`](#setmethodreturn). + +You do not need to unset the return value for a method that was set with [`setMethodReturn`](#setmethodreturn) +using `unsetMethodReturn` explicitly: the trait will take care of cleaning up all the modifications made to the +functions, methods and class attributes after each test. + +#### setFunctionHook + +`setFunctionHook(string $function, Closure $hook): void` + +Execute `$hook` when entering the function `$function`. + +Hooks can be set on both internal and user-defined functions. + +```php +setFunctionHook( + 'header', + function($header, bool $replace = true, int $response_code = 0) use (&$log): void { + $log[] = $header; + } + ); + + header('X-Plugin-Version: 1.0.0'); + header('X-Plugin-REST-Enabled: 1'); + header('X-Plugin-GraphQL-Enabled: 0'); + + $this->assertEquals([ + [ + 'X-Plugin-Version' => '1.0.0', + 'X-Plugin-REST-Enabled' => '1', + 'X-Plugin-GraphQL-Enabled' => '0' + ], $log); + } +} +``` + +#### unsetFunctionHook + +`unsetFunctionHook(string $function): void` + +Unset the hook for the function `$function` previously set with [`setFunctionHook`](#setfunctionhook). + +You do not need to unset the hook for a function that was set with [`setFunctionHook`](#setfunctionhook) +using `unsetFunctionHook` explicitly: the trait will take care of cleaning up all the modifications made to the +functions, methods and class attributes after each test. + +#### setMethodHook + +`setMethodHook(string $class, string $method, Closure $hook): void` + +Execute `$hook` when entering the static or instance method `$method` of the class `$class`. + +The keywords `self` and `$this` will be correctly bound to the class and the class instance respectively. + +```php +cachedItems === null){ + $this->cachedItems = wp_remote_get('https://example.com/items'); + } + + return array_slice($this->cachedItems, $from, $count); + } +} + +class MyTest extends WPTestCase +{ + use UopzFunctions; + + public function test_can_set_method_hook() + { + $connections = 0; + $this->setMethodHook( + LegacyApiController::class, + 'connect', + function() use (&$connections): void { + $connections = count(self::$connections) + 1; + } + ); + $itemsCacheHits = 0; + $this->setMethodHook( + LegacyApiController::class, + 'getItems', + function(int $count, int $from = 0) use (&$itemsCacheHit): bool { + if($this->cachedItems !== null){ + $itemsCacheHits++; + } + } + ); + + $connectedController1 = LegacyApiController::connect(); + $connectedController2 = LegacyApiController::connect(); + $connectedController1->getItems(10, 0); + $connectedController1->getItems(10, 10); + $connectedController2->getItems(10, 0); + $connectedController2->getItems(10, 10); + + $this->assertEquals(2, $connections); + $this->assertEquals(4, $itemsCacheHits); + } +} +``` + +#### unsetMethodHook + +`unsetMethodHook(string $class, string $method): void` + +Unset the hook for the static or instance method `$method` of the class `$class` previously set +with [`setMethodHook`](#setmethodhook). + +You do not need to unset the hook for a method that was set with [`seMethodHook`](#setmethodhook) +using `unsetClassMethodHook` explicitly: the trait will take care of cleaning up all the modifications made to the +functions, methods and class attributes after each test. + +#### setConstant + +`setConstant(string $constant, mixed $value): void` + +Set the constant `$constant` to the value `$value`. + +If the constant is not already defined, it will be defined and set to the value `$value`. + +```php +setconstant('WP_ADMIN', true); + $this->setconstant('TEST_CONST', 23); + + $this->assertTrue(wp_is_admin()); + $this->assertEquals(23, TEST_CONST); + } +} +``` + +#### unsetConstant + +`unsetConstant(string $constant): void` + +Unset an existing constant or restores the original value of the constant if set with [`setConstant`](#setconstant). + +```php +assertTrue(is_admin()); + + $this->unsetConstant('WP_ADMIN'); + + $this->assertFalse(is_admin()); + } +} +``` + +You do not need to undefine a constant defined with [`setConstant`](#setconstant) using `unsetConstant` explicitly: the +trait will take care of cleaning up all the modifications made to the functions, methods and class attributes after each +test. + +#### setClassConstant + +`setClassConstant(string $class, string $constant, mixed $value): void` + +Set the constant `$constant` of the class `$class` to the value `$value`. + +If the class constant is not already defined, it will be defined and set to the value `$value`. + +```php +setClassConstant(MyPlugin::class, 'VERSION', '23.89.0'); + $this->setClassConstant(MyPlugin::class, 'NOT_EXISTING', 'TEST'); + + $this->assertEquals('23.89.0', MyPlugin::VERSION); + $this->assertEquals('TEST', MyPlugin::NOT_EXISTING); + } +} +``` + +#### unsetClassConstant + +`unsetClassConstant(string $class, string $constant): void` + +Restore the constant `$constant` of the class `$class` to its original value or removes it if it was not defined. + +You do not need to undefine a constant defined with [`setClassConstant`](#setclassconstant) +using `undefineClassConstant` explicitly: the trait will take care of cleaning up all the modifications made to the +functions, methods and class attributes after each test. + +#### setClassMock + +`setClassMock(string $class, string|object $mock): void` + +Use `$mock` instead of `$class` when creating new instances of the class `$class`. + +This method allows you to override magic methods as well as you would do with a normal class extension. + +```php +setClassMock(PaymentApi::class, MockPaymentApi::class); + + $paymentApi = new PaymentApi(); + $this->assertInstanceOf(MyPluginMock::class, $paymentApi); + $this->assertSame('23.89.0', $paymentApi::version()); + } +} +``` + +If you set the `$mock` to an object, then the same mock object will be used for all the new instances of the +class `$class`: + +```php +setClassMock(PaymentApi::class, $mockPaymentApi); + + $api1 = new PaymentApi(); + $this->assertSame($mockPaymentApi, $api1); + $this->assertSame([1, 23, 89], $api1->getIds()); + $api2 = new PaymentApi(); + $this->assertSame($mockPaymentApi, $api2); + $this->assertSame([1, 23, 89], $api2->getIds()); + } +} +``` + +The `$mock` class, or instance, is **not** required to be a subclass of the class `$class` by the trait; although it +might be required from the code you're testing by means of type hinting. + +If the class or method you would like to set a mock for is `final`, then you can combine this method with +the [`unsetClassFinalAttribute`](#unsetclassfinalattribute) +and [`unsetMethodFinalAttribute`](#unsetmethodfinalattribute) methods to avoid the final attribute being set on the +class: + +```php +unsetClassFinalAttribute(LegacyPaymentApi::class); + $mockPaymentApi = new class extends LegacyPaymentApi { + public function getIds(){ + return [1, 23, 89]; + } + }; + $this->setClassMock(LegacyPaymentApi::class, $mockPaymentApi); + $this->unsetMethodFinalAttribute(LegacyCacheController::class, 'get'); + $mockCacheController = new class extends LegacyCacheController { + public function get(string $key){ + return 'some-value'; + } + }; + + $paymentApi = new LegacyPaymentApi(); + + $this->assertSame($mockPaymentApi, $paymentApi); + $this->assertSame([1, 23, 89], $paymentApi->getIds()); + + $cacheController = new LegacyCacheController(); + + $this->assertSame($mockCacheController, $cacheController); + $this->assertSame('some-value', $cacheController->get('some-key')); + } +} +``` + +#### unsetClassMock + +`unsetClassMock(string $class): void` + +Remove the mock for the class `$class` previously set with `setMock`. + +```php +setClassMock(MyPlugin::class, new MyPluginMock()); + + $this->assertInstanceOf(MyPluginMock::class, new MyPlugin()); + + $this->unsetClassMock(MyPlugin::class); + + $this->assertInstanceOf(MyPlugin::class, new MyPlugin()); + } +} +``` + +You do not need to unset the mock for a class that was set with [`setClassMock`](#setclassmock) using `unsetClassMock` +explicitly: the trait will take care of cleaning up all the modifications made to the functions, methods and class +attributes after each test. + +#### unsetClassFinalAttribute + +`unsetClassFinalAttribute(string $class): void` + +Remove the `final` attribute from the class `$class`. + +```php +post->createAndGet(); + + $this->unsetClassFinalAttribute(LegacyPaymentApi::class); + + // The class is not final anymore; it can be extended for testing purposes. + $mockPaymentApi = new class extends LegacyPaymentApi { + public function getIds(){ + return [1, 23, 89]; + } + }; + + $this->assertSame([1, 23, 89], $mockPaymentApi->getIds()); + } +} +``` + +#### resetClassFinalAttribute + +`resetClassFinalAttribute(string $class): void` + +Reset the `final` attribute of the class `$class` previously removed with +the [`unsetClassFinalAttribute`](#unsetclassfinalattribute) method. + +You do not need to restore the class final attribute for a class that was set +with [`unsetClassFinalAttribute`](#unsetclassfinalattribute) using `setClassFinalAttribute` explicitly: the trait will +take care of cleaning up all the modifications made to the functions, methods and class attributes after each test. + +#### unsetMethodFinalAttribute + +`unsetMethodFinalAttribute(string $class, string $method): void` + +Remove the `final` attribute from the static or instance method `$method` of the class `$class`. + +```php +unsetMethodFinalAttribute(LegacyAjaxController::class, 'printResponseAndExit'); + + // Build a class to avoid the `printResponseAndExit` method from exiting. + $testLegacyAdminController = new class extends LegacyAjaxController { + public string $response = ''; + + public function printResponseAndExit(){ + $this->response = $this->template->render('list', return: true); + return; + } + }; + + // Set up things for the test ... + + $testLegacyAjaxController->printResponseAndExit(); + + $this->assertEquals('', $testLegacyAjaxController->response); + } +} +``` + +#### restoreMethodFinalAttribute + +`restoreMethodFinalAttribute(string $class, string $method): void` + +Restore the `final` attribute of the method static or instance `$method` of the class `$class` previously removed with +the [`unsetMethodFinalAttribute`](#unsetmethodfinalattribute) method. + +You do not need to restore the method final attribute for a method that was set +with [`unsetMethodFinalAttribute`](#unsetmethodfinalattribute) using `restoreMethodFinalAttribute` explicitly: the trait +will take care of cleaning up all the modifications made to the functions, methods and class attributes after each test. + +#### addClassMethod + +`addClassMethod(string $class, string $method, Closure $closure, bool $static = false): void` + +Add a `public` static (`$static = true`) or instance (`$static = false`) method to the class `$class` with the +name `$method` and the code provided by the closure `$closure`. + +Differently from the [`setClassMock`](#setclassmock) method, this method will work on **already existing instances** of +the class `$class`, not just new instances. + +The closure `$this` will be bound to the class instance. + +```php +cache === null){ + $this->cache = wp_remote_get('https://example.com/items'); + $this->cacheCount = count($cache); + } + + return array_slice($this->cache, $from, $count); + } +} + +class MyTest extends WPTestCase +{ + use UopzFunctions; + + public function test_can_add_class_method() + { + $controller = LegacySingletonController::getInstance(); + + $this->addClassMethod( + LegacySingletonController::class, + 'setCache', + function(array $cache): void { + $this->cache = $cache; + $this->cacheCount = count($cache); + } + ); + + // Set the singletong instance cache for testing purposes. + $controller->setCache(range(1,100)); + + $this->assertEquals([1,2,3], $controller->getItems(3, 0)); + } +``` + +#### removeClassMethod + +`removeClassMethod(string $class, string $method): void` + +Remove the static or instance method `$method` added with [`addClassMethod`](#addclassmethod) from the class `$class`. + +You do not need to remove a method added with [`addClassMethod`](#addclassmethod), +or [`addClassStaticMethod`](#addclassstaticmethod), using `removeClassMethod` explicitly: the trait will take care of +cleaning up all the modifications made to the functions, methods and class attributes after each test. + +#### setObjectProperty + +`setObjectProperty(string|object $classOrObject, string $property, mixed $value): void` + +If `$classOrInstance` is a string, set the property `$property` of the class `$classOrObject` to the value `$value`. +If `$classOrInstance` is an object, set the property `$property` of the object `$classOrObject` to the value `$value`. + +```php +uuid = UUID::generate(); + } + + public function getHash(): string { + return wp_hash(serialize([ + 'uuid' => $this->uuid + 'from' => $this->from, + 'to' => $this->to + ])); + } +} + +class MyTest extends WPTestCase +{ + use UopzFunctions; + + public function test_can_set_object_property() + { + $payment = new Payment('Bob', 'Alice'); + + $this->setObjectProperty($payment, 'uuid', '550e8400-e29b-41d4-a716-446655440000'); + + $this->assertEquals(wp_hash(serialize([ + 'uuid' => '550e8400-e29b-41d4-a716-446655440000', + 'from' => 'Bob', + 'to' => 'Alice' + ])), $payment->getHash()); + } +} +``` + +You do not need to reset the property of an object that was set with `setObjectProperty` explicitly: the trait will take +care of cleaning up all the modifications made to the functions, methods and class attributes after each test. + +#### getObjectProperty + +`getObjectProperty(object $object, string $property): mixed` + +Get the value of the static or instance property `$property` of the object `$object`. + +```php +template = new Template(); + } + + // ... +} + +class MyTest extends WPTestCase +{ + use UopzFunctions; + + public function test_can_get_object_property() + { + $controller = new LegacyController(); + + $templateEngine $this->getObjectProperty($controller, 'template')); + + // ... do something with the template ... + } +} +``` + +#### getMethodStaticVariables + +`getMethodStaticVariables(string $class, string $method): array` + +Get the value of the static variables of the class `$class` and method `$method`. + +The method will work for both static and instance methods of the class `$class`. + +```php +log(200, 'OK'); + $requestLogger->log(403, 'Forbidden'); + $requestLogger->log(200, 'OK'); + $buffer = ob_get_clean(); + + $requestId = $this->getClassMethodStaticVariables(RequestLogger::class, 'log')['requestId']; + + $this->assertEquals("Request $requestId: 200 OK\nRequest $requestId: 403 Forbidden\nRequest $requestId: 200 OK\n", $buffer); +} +``` + +#### setMethodStaticVariables + +`setMethodStaticVariables(string $class, string $method, array $values): void` + +Set the static variablesof the class `$class` and method `$method` to the values `$values`. + +This will work on both static and instance methods. + +```php +
  • Item One
  • Item Two
  • '; + } +} + +class MyTest extends WPTestCase +{ + use UopzFunctions; + + public function test_can_set_class_method_static_variables() + { + $newValues = array_merge( + $this->getMethodStaticVariables(ListComponent::class, 'render'), + ['hash' => 'some-hash'] + ); + $this->setClassMethodStaticVariables( ListComponent::class, 'render', [ + 'hash' => 'some-hash' + ]); + + $component = new ListComponent(); + + $this->assertEquals( + '', + $component->render() + ); + } +} +``` + +You do not need to reset the static variable of a class method that was set with `setMethodStaticVariables` explicitly +using `resetMethodStaticVariables` explicitly: the trait will take care of cleaning up all the modifications made to the +functions, methods and class attributes after each test. + +#### resetMethodStaticVariables + +`resetMethodStaticVariables(string $class, string $method): void` + +Resets the static variables of the class `$class` method `$method` to their original values. + +#### setFunctionStaticVariable + +`setFunctionStaticVariables(string $function, string $variable, mixed $value): void` + +Set the static variable `$variable` of the function `$function` to the value `$value`. + +```php +Some HTML

    '; + + $rendered = true; + + return $html; +} + +class MyTest extends WPTestCase +{ + use UopzFunctions; + + public function test_can_set_function_static_variables() + { + $this->setFunctionStaticVariables('renderScreen', ['rendered' => false]); + + $this->assertEquals('

    Some HTML

    ', renderScreen()); + } +} +``` + +You do not need to reset the value of a function static variable set using the `resetFunctionStaticVariables` method +explicitly: the trait will take care of cleaning up all the modifications made to the functions, methods and class +attributes after each test. + +#### getFunctionStaticVariables + +`getFunctionStaticVariables(string $function, ): array` + +Get the value of the static variable `$variable` of the function `$function`. + +```php +Some HTML

    '; +} + +class MyTest extends WPTestCase +{ + use UopzFunctions; + + public function test_can_set_function_static_variables() + { + $screenHash = $this->getFunctionStaticVariables('renderScreen')['screenHash']; + + $this->assertEquals('

    Some HTML

    ', renderScreen()); + } +} +``` + +#### resetFunctionStaticVariables + +`resetFunctionStaticVariables(string $function): void` + +Resets the static variables of the function `$function` set with the `setFunctionStaticVariables` method. + +#### addFunction + +`addFunction(string $function, Closure $closure): void` + +Add a global or namespaced function to the current scope. + +```php +addFunction('myGlobalFunction', fn() => 23); + $this->addFunction('Acme\Project\namespacedFunction', fn() => 89); + + $this->assertEquals(23, myGlobalFunction()); + $this->assertEquals(89, Acme\Project\namespacedFunction()); + } +} +``` + +#### removeFunction + +`removeFunction(string $function): void` + +Removes the global or namespaced function `$function` from the current scope. +This will work for functions defined using the [`addFunction`](#addfunction) method or defined elsewhere. + +```php +addFunction('myGlobalFunction', fn() => 23); + $this->addFunction('Acme\Project\namespacedFunction', fn() => 89); + + $this->removeFunction('some_plugin_function'); + $this->removeFunction('Acme\Project\namespacedFunction'); + + // Added with addFunction. + $this->assertFalse(function_exists('myGlobalFunction'); + $this->assertFalse(function_exists('Acme\Project\namespacedFunction'); + + $this->assertFalse(function_exists('some_plugin_function'); + $this->assertFalse(function_exists('Another\Plugin\some_function'); + } +} +``` + +You do not need to remove a function added with [`addFunction`](#addfunction) using `removeFunction` explicitly: the +trait will take care of cleaning up all the modifications made to the functions, methods and class attributes after each +test. + +#### preventExit + +`preventExit(): void` + +Prevents `exit` or `die` calls executed after the method from terminating the PHP process calling `exit` or `die`. + +```php +preventExit(); + + ob_start(); + printAndDie(); + $buffer = ob_get_clean(); + + $this->assertEquals('Some HTML', $buffer); + } +} +``` + +#### restoreExit + +`allowExit(): void` + +Restores the original behavior of the `exit` and `die` functions. + +You do not need to restore the exit behavior for a exit that was prevented using [`preventExit`](#preventexit) +using `allowExit` explicitly: the trait will take care of cleaning up all the modifications made to the functions, +methods and class attributes after each test. + +[1]: https://www.php.net/manual/en/book.uopz.php + +[2]: https://pecl.php.net/package/uopz + +[3]: https://github.com/lucatume/function-mocker + +[4]: https://www.php.net/manual/en/class.streamwrapper.php + +[5]: https://blog.benoitblanchon.fr/build-php-extension-on-windows/ + +[6]: https://www.php.net/manual/en/install.pecl.windows.php + +[7]: https://github.com/krakjoe/uopz/blob/master/INSTALL.md + +[8]: https://github.com/lucatume/wp-browser/blob/78a5b5a691170a27b807a75ae063131e1ba3a87e/.github/workflows/test.yaml + +[9]: https://xdebug.org diff --git a/mkdocs.yml b/mkdocs.yml index 765781ed1..5f4307231 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -44,6 +44,8 @@ nav: - generate:wpxmlrpc: 'commands#generatewpxmlrpc' - monkey:cache:path: 'commands#monkeycachepath' - monkey:cache:clear: 'commands#monkeycacheclear' + - Testing Helpers: + - UopzFunctions trait: 'traits/UopzFunctions.md' - Troubleshooting: 'troubleshooting.md' - Sponsorship: 'https://github.com/sponsors/lucatume' - Changelog: 'https://github.com/lucatume/wp-browser/blob/master/CHANGELOG.md' @@ -90,6 +92,7 @@ theme: - navigation.top - toc.follow - content.code.select + - content.tabs.link logo: assets/logo.png favicon: assets/favicon.png palette: @@ -109,6 +112,9 @@ markdown_extensions: - pymdownx.inlinehilite - pymdownx.snippets - pymdownx.superfences + - admonition + - pymdownx.tabbed: + alternate_style: true extra: social: - icon: fontawesome/solid/paper-plane diff --git a/src/Traits/UopzFunctions.php b/src/Traits/UopzFunctions.php new file mode 100644 index 000000000..b14f667a0 --- /dev/null +++ b/src/Traits/UopzFunctions.php @@ -0,0 +1,504 @@ + + */ + private static array $uopzSetFunctionReturns = []; + + /** + * @var array + */ + private static array $uopzSetFunctionHooks = []; + + /** + * @var array + */ + private static array $uopzSetConstants = []; + + /** + * @var array + */ + private static array $uopzSetClassMocks = []; + + /** + * @var array + */ + private static array $uopzUnsetClassFinalAttribute = []; + + /** + * @var array + */ + private static array $uopzAddClassMethods = []; + + /** + * @var array + */ + private static array $uopzUnsetClassMethodFinalAttribute = []; + + /** + * @var array + */ + private static array $uopzSetObjectProperties = []; + + /** + * @var array> + */ + private static array $uopzSetMethodStaticVariables = []; + + /** + * @var array> + */ + private static array $uopzSetFunctionStaticVariables = []; + + /** + * @var array + */ + private static array $uopzAddedFunctions = []; + + private static ?bool $uopzAllowExit = null; + + protected function setFunctionReturn(string $function, mixed $value, bool $execute = false): void + { + if (!function_exists('uopz_set_return')) { + $this->markTestSkipped('This test requires the uopz extension'); + } + + uopz_set_return($function, $value, $execute); + self::$uopzSetFunctionReturns[$function] = true; + } + + protected function unsetFunctionReturn(string $function): void + { + if (!isset(self::$uopzSetFunctionReturns[$function])) { + return; + } + + uopz_unset_return($function); + unset(self::$uopzSetFunctionReturns[$function]); + } + + protected function setMethodReturn(string $class, string $method, mixed $value, bool $execute = false): void + { + $classAndMethod = "$class::$method"; + uopz_set_return($class, $method, $value, $execute); + self::$uopzSetFunctionReturns[$classAndMethod] = true; + } + + protected function unsetMethodReturn(string $class, string $method): void + { + $classAndMethod = "$class::$method"; + + if (!isset(self::$uopzSetFunctionReturns[$classAndMethod])) { + return; + } + + uopz_unset_return($class, $method); + unset(self::$uopzSetFunctionReturns[$classAndMethod]); + } + + protected function setFunctionHook(string $function, Closure $hook): void + { + if (!function_exists('uopz_set_hook')) { + $this->markTestSkipped('This test requires the uopz extension'); + } + + uopz_set_hook($function, $hook); + self::$uopzSetFunctionHooks[$function] = true; + } + + protected function unsetFunctionHook(string $function): void + { + if (!isset(self::$uopzSetFunctionHooks[$function])) { + return; + } + + uopz_unset_hook($function); + unset(self::$uopzSetFunctionHooks[$function]); + } + + protected function setMethodHook(string $class, string $method, Closure $hook): void + { + if (!function_exists('uopz_set_hook')) { + $this->markTestSkipped('This test requires the uopz extension'); + } + + $classAndMethod = "$class::$method"; + uopz_set_hook($class, $method, $hook); + self::$uopzSetFunctionHooks[$classAndMethod] = true; + } + + protected function unsetMethodHook(string $class, string $method): void + { + $classAndMethod = "$class::$method"; + + if (!isset(self::$uopzSetFunctionHooks[$classAndMethod])) { + return; + } + + uopz_unset_hook($class, $method); + unset(self::$uopzSetFunctionHooks[$classAndMethod]); + } + + protected function setConstant(string $constant, mixed $value): void + { + if (!function_exists('uopz_redefine')) { + $this->markTestSkipped('This test requires the uopz extension'); + } + + $previousValue = defined($constant) ? constant($constant) : '__NOT_PREVIOUSLY_DEFINED__'; + if ($previousValue === '__NOT_PREVIOUSLY_DEFINED__') { + define($constant, $value); + } else { + uopz_redefine($constant, $value); + } + self::$uopzSetConstants[$constant] = $previousValue; + } + + protected function unsetConstant(string $constant): void + { + if (!isset(self::$uopzSetConstants[$constant])) { + return; + } + + $previousValue = self::$uopzSetConstants[$constant]; + + if ($previousValue !== '__NOT_PREVIOUSLY_DEFINED__') { + uopz_redefine($constant, $previousValue); + } else { + uopz_undefine($constant); + } + unset(self::$uopzSetConstants[$constant]); + } + + protected function setClassConstant(string $class, string $constant, mixed $value): void + { + if (!function_exists('uopz_redefine')) { + $this->markTestSkipped('This test requires the uopz extension'); + } + + $previousValue = defined("$class::$constant") ? + constant("$class::$constant") + : '__NOT_PREVIOUSLY_DEFINED__'; + uopz_redefine($class, $constant, $value); + self::$uopzSetConstants["$class::$constant"] = $previousValue; + } + + protected function unsetClassConstant(string $class, string $constant): void + { + if (!isset(self::$uopzSetConstants["$class::$constant"])) { + return; + } + + $previousValue = self::$uopzSetConstants["$class::$constant"]; + + if ($previousValue !== '__NOT_PREVIOUSLY_DEFINED__') { + uopz_redefine($class, $constant, $previousValue); + } else { + uopz_undefine($class, $constant); + } + unset(self::$uopzSetConstants["$class::$constant"]); + } + + protected function setClassMock(string $class, mixed $mock): void + { + if (!function_exists('uopz_set_mock')) { + $this->markTestSkipped('This test requires the uopz extension'); + } + + uopz_set_mock($class, $mock); + self::$uopzSetClassMocks[$class] = true; + } + + protected function unsetClassMock(string $class): void + { + if (!isset(self::$uopzSetClassMocks[$class])) { + return; + } + + uopz_unset_mock($class); + unset(self::$uopzSetClassMocks[$class]); + } + + protected function unsetClassFinalAttribute(string $class): void + { + if (!function_exists('uopz_unset_return')) { + $this->markTestSkipped('This test requires the uopz extension'); + } + + $flags = uopz_flags($class, ''); + uopz_flags($class, '', $flags & ~ZEND_ACC_FINAL); + self::$uopzUnsetClassFinalAttribute[$class] = true; + } + + protected function resetClassFinalAttribute(string $class): void + { + if (!isset(self::$uopzUnsetClassFinalAttribute[$class])) { + return; + } + + $flags = uopz_flags($class, ''); + uopz_flags($class, '', $flags | ZEND_ACC_FINAL); + unset(self::$uopzUnsetClassFinalAttribute[$class]); + } + + protected function unsetMethodFinalAttribute(string $class, string $method): void + { + if (!function_exists('uopz_unset_return')) { + $this->markTestSkipped('This test requires the uopz extension'); + } + + $flags = uopz_flags($class, $method); + uopz_flags($class, $method, $flags & ~ZEND_ACC_FINAL); + self::$uopzUnsetClassMethodFinalAttribute["$class::$method"] = true; + } + + protected function resetMethodFinalAttribute(string $class, string $method): void + { + $classAndMethod = "$class::$method"; + if (!isset(self::$uopzUnsetClassMethodFinalAttribute[$classAndMethod])) { + return; + } + + $flags = uopz_flags($class, $method); + uopz_flags($class, $method, $flags | ZEND_ACC_FINAL); + unset(self::$uopzUnsetClassMethodFinalAttribute[$classAndMethod]); + } + + protected function addClassMethod(string $class, string $method, Closure $closure, bool $static = false): void + { + if (!function_exists('uopz_add_function')) { + $this->markTestSkipped('This test requires the uopz extension'); + } + + $flags = ZEND_ACC_PUBLIC; + if ($static) { + $flags |= ZEND_ACC_STATIC; + } + uopz_add_function($class, $method, $closure, $flags); + self::$uopzAddClassMethods["$class::$method"] = true; + } + + protected function removeClassMethod(string $class, string $method): void + { + $classAndMethod = "$class::$method"; + if (!isset(self::$uopzAddClassMethods[$classAndMethod])) { + return; + } + + uopz_del_function($class, $method); + unset(self::$uopzAddClassMethods[$classAndMethod]); + } + + protected function setObjectProperty( + string|object $classOrObject, + string $property, + mixed $value + ): void { + if (!function_exists('uopz_set_property')) { + $this->markTestSkipped('This test requires the uopz extension'); + } + + $previousValue = uopz_get_property($classOrObject, $property); + uopz_set_property($classOrObject, $property, $value); + $id = is_string($classOrObject) ? $classOrObject : spl_object_hash($classOrObject); + self::$uopzSetObjectProperties["$id::$property"] = [$previousValue, $classOrObject]; + } + + protected function getObjectProperty(string|object $classOrObject, string $property): mixed + { + if (!function_exists('uopz_get_property')) { + $this->markTestSkipped('This test requires the uopz extension'); + } + + return uopz_get_property($classOrObject, $property); + } + + protected function resetObjectProperty(string|object $classOrObject, string $property): void + { + $id = is_string($classOrObject) ? $classOrObject : spl_object_hash($classOrObject); + + if (!isset(self::$uopzSetObjectProperties["$id::$property"])) { + return; + } + + [$previousValue, $classOrObject] = self::$uopzSetObjectProperties["$id::$property"]; + uopz_set_property($classOrObject, $property, $previousValue); + unset(self::$uopzSetObjectProperties["$id::$property"]); + } + + /** + * @param array $values + */ + protected function setMethodStaticVariables(string $class, string $method, array $values): void + { + if (!function_exists('uopz_set_static')) { + $this->markTestSkipped('This test requires the uopz extension'); + } + + $currentValues = uopz_get_static($class, $method); + + if (!isset(self::$uopzSetMethodStaticVariables["$class::$method"])) { + self::$uopzSetMethodStaticVariables["$class::$method"] = $currentValues; + } + + uopz_set_static($class, $method, $values); + } + + /** + * @return array + */ + protected function getMethodStaticVariables(string $class, string $method): array + { + if (!function_exists('uopz_get_static')) { + $this->markTestSkipped('This test requires the uopz extension'); + } + + return uopz_get_static($class, $method); + } + + protected function resetMethodStaticVariables(string $class, string $method): void + { + if (!isset(self::$uopzSetMethodStaticVariables["$class::$method"])) { + return; + } + + $staticVariables = self::$uopzSetMethodStaticVariables["$class::$method"]; + uopz_set_static($class, $method, $staticVariables); + unset(self::$uopzSetMethodStaticVariables["$class::$method"]); + } + + /** + * @return array + */ + protected function getFunctionStaticVariables(string $function): array + { + if (!function_exists('uopz_get_static')) { + $this->markTestSkipped('This test requires the uopz extension'); + } + + return uopz_get_static($function); + } + + /** + * @param array $values + */ + protected function setFunctionStaticVariables(string $function, array $values): void + { + if (!function_exists('uopz_set_static')) { + $this->markTestSkipped('This test requires the uopz extension'); + } + + $currentValues = uopz_get_static($function); + + if (!isset(self::$uopzSetFunctionStaticVariables[$function])) { + self::$uopzSetFunctionStaticVariables[$function] = $currentValues; + } + + uopz_set_static($function, array_merge($currentValues, $values)); + } + + protected function resetFunctionStaticVariables(string $function): void + { + if (!isset(self::$uopzSetFunctionStaticVariables[$function])) { + return; + } + + $staticVariables = self::$uopzSetFunctionStaticVariables[$function]; + uopz_set_static($function, $staticVariables); + unset(self::$uopzSetFunctionStaticVariables[$function]); + } + + protected function addFunction(string $function, Closure $handler): void + { + if (!function_exists('uopz_add_function')) { + $this->markTestSkipped('This test requires the uopz extension'); + } + + self::$uopzAddedFunctions[$function] = true; + uopz_add_function($function, $handler); + } + + protected function removeFunction(string $function): void + { + if (!function_exists('uopz_del_function')) { + $this->markTestSkipped('This test requires the uopz extension'); + } + + if (!isset(self::$uopzAddedFunctions[$function])) { + return; + } + + uopz_del_function($function); + unset(self::$uopzAddedFunctions[$function]); + } + + protected function preventExit(): void + { + if (!function_exists('uopz_allow_exit')) { + $this->markTestSkipped('This test requires the uopz extension'); + } + + uopz_allow_exit(false); + self::$uopzAllowExit = false; + } + + protected function allowExit(): void + { + if (self::$uopzAllowExit === true) { + return; + } + + if (!function_exists('uopz_allow_exit')) { + $this->markTestSkipped('This test requires the uopz extension'); + } + + uopz_allow_exit(true); + self::$uopzAllowExit = true; + } + + /** + * @after + */ + public function resetUopzAlterations(): void + { + foreach (self::$uopzSetFunctionReturns as $function => $k) { + $this->unsetFunctionReturn($function); + } + + foreach (self::$uopzSetFunctionHooks as $function => $k) { + $this->unsetFunctionHook($function); + } + + foreach (self::$uopzSetConstants as $constant => $k) { + $this->unsetConstant($constant); + } + + foreach (self::$uopzSetClassMocks as $class => $k) { + $this->unsetClassMock($class); + } + + foreach (self::$uopzSetObjectProperties as $idAndProperty => [$previousValue, $classOrObject]) { + [, $property] = explode('::', $idAndProperty); + $this->resetObjectProperty($classOrObject, $property); + } + + foreach (self::$uopzSetMethodStaticVariables as $classAndMethod => $values) { + [$class, $method] = explode('::', $classAndMethod); + $this->resetMethodStaticVariables($class, $method); + } + + foreach (self::$uopzSetFunctionStaticVariables as $function => $values) { + $this->resetFunctionStaticVariables($function); + } + + foreach (self::$uopzAddedFunctions as $function => $k) { + $this->removeFunction($function); + } + } +} diff --git a/tests/_data/uopz-test/functions.php b/tests/_data/uopz-test/functions.php new file mode 100644 index 000000000..6356ad46b --- /dev/null +++ b/tests/_data/uopz-test/functions.php @@ -0,0 +1,69 @@ +markTestSkipped('This test requires the uopz extension'); - } - - $replaced = []; - foreach ($what as $key => $value) { - if (function_exists($key)) { - uopz_set_return($key, $value, is_callable($value)); - $replaced[$key] = 'func'; - continue; - } elseif (strpos($key, '::')) { - list($class, $method) = explode('::', $key, 2); - uopz_set_return($class, $method, $value, is_callable($value)); - $replaced[$key] = 'static-method'; - continue; - } - - throw new RuntimeException("{$key} is neither a function nor a static method."); - } - - try { - $do(); - } catch (Exception $e) { - foreach ($replaced as $key => $type) { - if ($type === 'func') { - uopz_unset_return($key); - } elseif ($type === 'static-method') { - list($class, $method) = explode('::', $key, 2); - uopz_unset_return($class, $method); - } - } - throw $e; - } - } - - protected function uopzSetFunctionReturn(string $function, $return, bool $execute = false): void - { - if (!function_exists('uopz_set_return')) { - $this->markTestSkipped('This test requires the uopz extension'); - } - - uopz_set_return($function, $return, $execute); - self::$uopzSetFunctionReturns[] = $function; - } - - protected function uopzSetStaticMethodReturn( - string $class, - string $method, - mixed $return, - bool $execute = false - ): void { - if (!function_exists('uopz_set_return')) { - $this->markTestSkipped('This test requires the uopz extension'); - } - - uopz_set_return($class, $method, $return, $execute); - self::$uopzSetStaticMethodReturns[] = $class . '::' . $method; - } - - protected function uopzRedefineConstant(string $constant, string|int|bool|null $value): void - { - if (!function_exists('uopz_set_return')) { - $this->markTestSkipped('This test requires the uopz extension'); - } - - uopz_redefine($constant, $value); - $wasDefined = defined($constant); - $previousValue = $wasDefined ? constant($constant) : null; - self::$uopzRedefinedConstants[$constant] = [$wasDefined, $previousValue]; - } - - protected function uopzRedefinedClassConstant(string $class, string $constant, string|int|bool|null $value): void - { - if (!function_exists('uopz_set_return')) { - $this->markTestSkipped('This test requires the uopz extension'); - } - - uopz_redefine($class, $constant, $value); - $wasDefined = defined($class . '::' . $constant); - $previousValue = $wasDefined ? constant($class . '::' . $constant) : null; - self::$uopzRedefinedClassConstants[$class . '::' . $constant] = [$wasDefined, $previousValue]; - } - - /** - * @after - */ - public function uopzTearDown(): void - { - foreach (self::$uopzSetFunctionReturns as $function) { - uopz_unset_return($function); - } - self::$uopzSetFunctionReturns = []; - - foreach (self::$uopzSetStaticMethodReturns as $classAndMethod) { - [$class, $function] = explode('::', $classAndMethod, 2); - uopz_unset_return($class, $function); - } - self::$uopzSetStaticMethodReturns = []; - - foreach (self::$uopzRedefinedConstants as $constant => [$wasDefined, $previousValue]) { - uopz_undefine($constant); - if ($wasDefined) { - uopz_redefine($constant, $previousValue); - } - } - self::$uopzRedefinedConstants = []; - - foreach (self::$uopzRedefinedClassConstants as $classAndConstant => [$wasDefined, $previousValue]) { - [$class, $constant] = explode($classAndConstant, '::', 2); - uopz_undefine($class, $constant); - if ($wasDefined) { - uopz_redefine($class, $constant, $previousValue); - } - } - self::$uopzRedefinedClassConstants = []; - - foreach (self::$uopzSetMocks as $class) { - uopz_unset_mock($class); - } - self::$uopzSetMocks = []; - } - - protected function uopzUndefineConstant(string $constant): void - { - if (!function_exists('uopz_set_return')) { - $this->markTestSkipped('This test requires the uopz extension'); - } - - if (!defined($constant)) { - // The constant was not defined, let's make sure to reset during tear down. - self::$uopzRedefinedConstants[$constant] = [false, null]; - return; - } - - self::$uopzRedefinedConstants[$constant] = [true, constant($constant)]; - - uopz_undefine($constant); - } - - protected function uopzSetMock(string $class, string|object $mock): void - { - if (!function_exists('uopz_set_return')) { - $this->markTestSkipped('This test requires the uopz extension'); - } - - self::$uopzSetMocks[] = $class; - if ($mock instanceof Generator) { - $mock = $mock->current(); - } - uopz_set_mock($class, $mock); - } - - protected function uopzUnsetMock(string $class): void - { - if (!function_exists('uopz_unset_mock')) { - $this->markTestSkipped('This test requires the uopz extension'); - } - - $index = array_search($class, self::$uopzSetMocks, true); - - if ($index === false) { - return; - } - - unset(self::$uopzSetMocks[$index]); - uopz_unset_mock($class); - } - - protected function uopzUnsetFunctionReturn(string $string) - { - if (!function_exists('uopz_unset_return')) { - $this->markTestSkipped('This test requires the uopz extension'); - } - - $index = array_search($string, self::$uopzSetFunctionReturns, true); - - if ($index === false) { - return; - } - - uopz_unset_return($string); - unset(self::$uopzSetFunctionReturns[$index]); - } -} diff --git a/tests/unit/lucatume/WPBrowser/Command/DevInfoTest.php b/tests/unit/lucatume/WPBrowser/Command/DevInfoTest.php index f59ad8da6..19a35a694 100644 --- a/tests/unit/lucatume/WPBrowser/Command/DevInfoTest.php +++ b/tests/unit/lucatume/WPBrowser/Command/DevInfoTest.php @@ -9,7 +9,7 @@ use lucatume\WPBrowser\Extension\DockerComposeController; use lucatume\WPBrowser\Extension\EventDispatcherBridge; use lucatume\WPBrowser\Tests\Traits\ClassStubs; -use lucatume\WPBrowser\Tests\Traits\UopzFunctions; +use lucatume\WPBrowser\Traits\UopzFunctions; use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Output\BufferedOutput; use tad\Codeception\SnapshotAssertions\SnapshotAssertions; @@ -48,7 +48,7 @@ public function should_print_information_about_each_service_extension_found_in_c { $builtInServerControllerBuildArgs = null; - $this->uopzSetMock(BuiltInServerController::class, + $this->setClassMock(BuiltInServerController::class, $this->makeEmptyClass(BuiltInServerController::class, [ '__construct' => function () use (&$builtInServerControllerBuildArgs) { $builtInServerControllerBuildArgs = func_get_args(); @@ -64,7 +64,7 @@ public function should_print_information_about_each_service_extension_found_in_c } ])); $dockerComposeControllerBuildArgs = null; - $this->uopzSetMock(DockerComposeController::class, + $this->setClassMock(DockerComposeController::class, $this->makeEmptyClass(DockerComposeController::class, [ '__construct' => function () use (&$dockerComposeControllerBuildArgs) { $dockerComposeControllerBuildArgs = func_get_args(); @@ -80,7 +80,7 @@ public function should_print_information_about_each_service_extension_found_in_c } ])); $chromeDriverControllerBuildArgs = null; - $this->uopzSetMock(ChromeDriverController::class, + $this->setClassMock(ChromeDriverController::class, $this->makeEmptyClass(ChromeDriverController::class, [ '__construct' => function () use (&$chromeDriverControllerBuildArgs) { $chromeDriverControllerBuildArgs = func_get_args(); diff --git a/tests/unit/lucatume/WPBrowser/Command/DevStartTest.php b/tests/unit/lucatume/WPBrowser/Command/DevStartTest.php index 7041927d8..0a8de1bed 100644 --- a/tests/unit/lucatume/WPBrowser/Command/DevStartTest.php +++ b/tests/unit/lucatume/WPBrowser/Command/DevStartTest.php @@ -9,7 +9,7 @@ use lucatume\WPBrowser\Extension\DockerComposeController; use lucatume\WPBrowser\Extension\EventDispatcherBridge; use lucatume\WPBrowser\Tests\Traits\ClassStubs; -use lucatume\WPBrowser\Tests\Traits\UopzFunctions; +use lucatume\WPBrowser\Traits\UopzFunctions; use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Output\BufferedOutput; @@ -46,7 +46,7 @@ public function should_start_each_service_extension_found_in_configuration(): vo { $builtInServerControllerBuildArgs = null; $builtInServerControllerStarted = false; - $this->uopzSetMock(BuiltInServerController::class, + $this->setClassMock(BuiltInServerController::class, $this->makeEmptyClass(BuiltInServerController::class, [ '__construct' => function () use (&$builtInServerControllerBuildArgs) { $builtInServerControllerBuildArgs = func_get_args(); @@ -57,7 +57,7 @@ public function should_start_each_service_extension_found_in_configuration(): vo ])); $dockerComposeControllerBuildArgs = null; $dockerComposeControllerStarted = false; - $this->uopzSetMock(DockerComposeController::class, + $this->setClassMock(DockerComposeController::class, $this->makeEmptyClass(DockerComposeController::class, [ '__construct' => function () use (&$dockerComposeControllerBuildArgs) { $dockerComposeControllerBuildArgs = func_get_args(); @@ -68,7 +68,7 @@ public function should_start_each_service_extension_found_in_configuration(): vo ])); $chromeDriverControllerBuildArgs = null; $chromeDriverControllerStarted = false; - $this->uopzSetMock(ChromeDriverController::class, + $this->setClassMock(ChromeDriverController::class, $this->makeEmptyClass(ChromeDriverController::class, [ '__construct' => function () use (&$chromeDriverControllerBuildArgs) { $chromeDriverControllerBuildArgs = func_get_args(); diff --git a/tests/unit/lucatume/WPBrowser/Command/DevStopTest.php b/tests/unit/lucatume/WPBrowser/Command/DevStopTest.php index 7e76b20ab..353c7d022 100644 --- a/tests/unit/lucatume/WPBrowser/Command/DevStopTest.php +++ b/tests/unit/lucatume/WPBrowser/Command/DevStopTest.php @@ -9,7 +9,7 @@ use lucatume\WPBrowser\Extension\DockerComposeController; use lucatume\WPBrowser\Extension\EventDispatcherBridge; use lucatume\WPBrowser\Tests\Traits\ClassStubs; -use lucatume\WPBrowser\Tests\Traits\UopzFunctions; +use lucatume\WPBrowser\Traits\UopzFunctions; use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Output\BufferedOutput; @@ -46,7 +46,7 @@ public function should_start_each_service_extension_found_in_configuration(): vo { $builtInServerControllerBuildArgs = null; $builtInServerControllerStopped = false; - $this->uopzSetMock(BuiltInServerController::class, + $this->setClassMock(BuiltInServerController::class, $this->makeEmptyClass(BuiltInServerController::class, [ '__construct' => function () use (&$builtInServerControllerBuildArgs) { $builtInServerControllerBuildArgs = func_get_args(); @@ -57,7 +57,7 @@ public function should_start_each_service_extension_found_in_configuration(): vo ])); $dockerComposeControllerBuildArgs = null; $dockerComposeControllerStopped = false; - $this->uopzSetMock(DockerComposeController::class, + $this->setClassMock(DockerComposeController::class, $this->makeEmptyClass(DockerComposeController::class, [ '__construct' => function () use (&$dockerComposeControllerBuildArgs) { $dockerComposeControllerBuildArgs = func_get_args(); @@ -68,7 +68,7 @@ public function should_start_each_service_extension_found_in_configuration(): vo ])); $chromeDriverControllerBuildArgs = null; $crhomeDriverControllerStopped = false; - $this->uopzSetMock(ChromeDriverController::class, + $this->setClassMock(ChromeDriverController::class, $this->makeEmptyClass(ChromeDriverController::class, [ '__construct' => function () use (&$chromeDriverControllerBuildArgs) { $chromeDriverControllerBuildArgs = func_get_args(); diff --git a/tests/unit/lucatume/WPBrowser/Command/RunAllTest.php b/tests/unit/lucatume/WPBrowser/Command/RunAllTest.php index 419b4175d..8683a5aa4 100644 --- a/tests/unit/lucatume/WPBrowser/Command/RunAllTest.php +++ b/tests/unit/lucatume/WPBrowser/Command/RunAllTest.php @@ -9,7 +9,7 @@ use lucatume\WPBrowser\Adapters\Symfony\Component\Process\Process; use lucatume\WPBrowser\Command\RunAll; use lucatume\WPBrowser\Tests\Traits\ClassStubs; -use lucatume\WPBrowser\Tests\Traits\UopzFunctions; +use lucatume\WPBrowser\Traits\UopzFunctions; use PHPUnit\Framework\Assert; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\BufferedOutput; @@ -49,7 +49,7 @@ public function should_invoke_codecept_bin_once_for_each_suite(): void 'getIterator' => fn() => yield from ["Running suite\n", "Done\n"], 'isSuccessful' => fn() => true, ]; - $this->uopzSetMock(Process::class, $this->makeEmptyClass(Process::class, $mockParams)); + $this->setClassMock(Process::class, $this->makeEmptyClass(Process::class, $mockParams)); $this->uopzSetStaticMethodReturn(Configuration::class, 'suites', ['suite-1', 'suite-2', 'suite-3']); $command = new RunAll(); @@ -85,7 +85,7 @@ public function should_return_1_if_any_suite_fails(int $failingSuite, string $ex return $currentSuite++ !== $failingSuite; }, ]; - $this->uopzSetMock(Process::class, $this->makeEmptyClass(Process::class, $mockParams)); + $this->setClassMock(Process::class, $this->makeEmptyClass(Process::class, $mockParams)); $this->uopzSetStaticMethodReturn(Configuration::class, 'suites', ['suite-1', 'suite-2', 'suite-3']); $command = new RunAll(); @@ -103,7 +103,7 @@ public function should_return_1_if_any_suite_fails(int $failingSuite, string $ex */ public function should_return_1_if_failing_to_build_process(): void { - $this->uopzSetMock(Process::class, + $this->setClassMock(Process::class, $this->makeEmptyClass(Process::class, [ '__construct' => fn() => throw new Exception('Failed to build process.') ])); diff --git a/tests/unit/lucatume/WPBrowser/Events/DispatcherTest.php b/tests/unit/lucatume/WPBrowser/Events/DispatcherTest.php index 05af2c6a9..811a3f5a2 100644 --- a/tests/unit/lucatume/WPBrowser/Events/DispatcherTest.php +++ b/tests/unit/lucatume/WPBrowser/Events/DispatcherTest.php @@ -5,7 +5,7 @@ use Codeception\Events; use Codeception\Test\Unit; -use lucatume\WPBrowser\Tests\Traits\UopzFunctions; +use lucatume\WPBrowser\Traits\UopzFunctions; use Psr\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventDispatcher; diff --git a/tests/unit/lucatume/WPBrowser/Events/Module/WPDbTest.php b/tests/unit/lucatume/WPBrowser/Events/Module/WPDbTest.php index c375be975..8dc674a0a 100644 --- a/tests/unit/lucatume/WPBrowser/Events/Module/WPDbTest.php +++ b/tests/unit/lucatume/WPBrowser/Events/Module/WPDbTest.php @@ -9,12 +9,9 @@ use lucatume\WPBrowser\Module\Support\DbDump; use lucatume\WPBrowser\Tests\Traits\LoopIsolation; use lucatume\WPBrowser\Tests\Traits\TmpFilesCleanup; -use lucatume\WPBrowser\Tests\Traits\UopzFunctions; +use lucatume\WPBrowser\Traits\UopzFunctions; use lucatume\WPBrowser\Utils\Env; use lucatume\WPBrowser\Utils\Filesystem as FS; -use lucatume\WPBrowser\WordPress\Database\SQLiteDatabase; -use lucatume\WPBrowser\WordPress\Installation; -use lucatume\WPBrowser\WordPress\InstallationState\InstallationStateInterface; use PDO; use RuntimeException; @@ -143,7 +140,7 @@ public function it_should_throw_is_specified_dump_file_is_not_readable(): void { $root = FS::tmpDir('wpdb_', ['dump.sql' => 'SELECT 1']); $filepath = $root . '/dump.sql'; - $this->uopzSetFunctionReturn('is_readable', static function (string $file) use ($filepath) { + $this->setFunctionReturn('is_readable', static function (string $file) use ($filepath) { return $file !== $filepath && is_readable($file); }, true); diff --git a/tests/unit/lucatume/WPBrowser/Events/Module/WPQueriesTest.php b/tests/unit/lucatume/WPBrowser/Events/Module/WPQueriesTest.php index 3dd524236..8d48d7e46 100644 --- a/tests/unit/lucatume/WPBrowser/Events/Module/WPQueriesTest.php +++ b/tests/unit/lucatume/WPBrowser/Events/Module/WPQueriesTest.php @@ -6,8 +6,7 @@ use Codeception\Lib\Di; use Codeception\Lib\ModuleContainer; use Codeception\Test\Unit; -use lucatume\WPBrowser\Tests\Traits\TmpFilesCleanup; -use lucatume\WPBrowser\Tests\Traits\UopzFunctions; +use lucatume\WPBrowser\Traits\UopzFunctions; use lucatume\WPBrowser\Utils\Env; use lucatume\WPBrowser\Utils\Filesystem as FS; use lucatume\WPBrowser\Utils\Random; diff --git a/tests/unit/lucatume/WPBrowser/Extension/BuiltInServerControllerTest.php b/tests/unit/lucatume/WPBrowser/Extension/BuiltInServerControllerTest.php index 4323e5c66..b352887a1 100644 --- a/tests/unit/lucatume/WPBrowser/Extension/BuiltInServerControllerTest.php +++ b/tests/unit/lucatume/WPBrowser/Extension/BuiltInServerControllerTest.php @@ -10,7 +10,7 @@ use Codeception\Test\Unit; use lucatume\WPBrowser\Extension\BuiltInServerController; use lucatume\WPBrowser\ManagedProcess\PhpBuiltInServer; -use lucatume\WPBrowser\Tests\Traits\UopzFunctions; +use lucatume\WPBrowser\Traits\UopzFunctions; use lucatume\WPBrowser\Utils\Composer; use lucatume\WPBrowser\Utils\Random; use stdClass; @@ -71,7 +71,7 @@ public function _before() $this->uopzSetStaticMethodReturn(Composer::class, 'binDir', $bin); // Silence output. $this->output = new Output(['verbosity' => Output::VERBOSITY_QUIET]); - $this->uopzSetMock(Output::class, $this->output); + $this->setClassMock(Output::class, $this->output); } public function notArrayOfStringsProvider(): array @@ -243,7 +243,7 @@ public function should_throw_if_config_env_is_not_associative_array_with_string_ */ public function should_replace_cc_root_dir_placeholder_in_env_array(): void { - $this->uopzSetMock(PhpBuiltInServer::class, PhpBuiltInServerMock::class); + $this->setClassMock(PhpBuiltInServer::class, PhpBuiltInServerMock::class); $config = [ 'docroot' => __DIR__, 'env' => [ @@ -291,7 +291,7 @@ public function should_handle_php_built_in_server_lifecycle(): void public function should_throw_if_pid_file_is_not_readable(): void { file_put_contents(PhpBuiltInServer::getPidFile(), '1233'); - $this->uopzSetFunctionReturn('file_get_contents', function (string $file): bool { + $this->setFunctionReturn('file_get_contents', function (string $file): bool { if ($file === PhpBuiltInServer::getPidFile()) { return false; } diff --git a/tests/unit/lucatume/WPBrowser/Extension/ChromeDriverControllerTest.php b/tests/unit/lucatume/WPBrowser/Extension/ChromeDriverControllerTest.php index 036e1354d..5a156b6dd 100644 --- a/tests/unit/lucatume/WPBrowser/Extension/ChromeDriverControllerTest.php +++ b/tests/unit/lucatume/WPBrowser/Extension/ChromeDriverControllerTest.php @@ -10,7 +10,7 @@ use Codeception\Test\Unit; use lucatume\WPBrowser\Extension\ChromeDriverController; use lucatume\WPBrowser\ManagedProcess\ChromeDriver; -use lucatume\WPBrowser\Tests\Traits\UopzFunctions; +use lucatume\WPBrowser\Traits\UopzFunctions; use lucatume\WPBrowser\Utils\Composer; use stdClass; use tad\Codeception\SnapshotAssertions\SnapshotAssertions; @@ -41,7 +41,7 @@ public function _before() $this->uopzSetStaticMethodReturn(Composer::class, 'binDir', $bin); // Silence output. $this->output = new Output(['verbosity' => Output::VERBOSITY_QUIET]); - $this->uopzSetMock(Output::class, $this->output); + $this->setClassMock(Output::class, $this->output); } /** @@ -205,7 +205,7 @@ public function should_handle_chromedriver_lifecycle(): void public function should_throw_if_pid_file_is_not_readable(): void { file_put_contents(ChromeDriver::getPidFile(), '1233'); - $this->uopzSetFunctionReturn('file_get_contents', function (string $file): bool { + $this->setFunctionReturn('file_get_contents', function (string $file): bool { if ($file === ChromeDriver::getPidFile()) { return false; } diff --git a/tests/unit/lucatume/WPBrowser/Extension/DockerComposeControllerTest.php b/tests/unit/lucatume/WPBrowser/Extension/DockerComposeControllerTest.php index 21e88448e..e1e45b81e 100644 --- a/tests/unit/lucatume/WPBrowser/Extension/DockerComposeControllerTest.php +++ b/tests/unit/lucatume/WPBrowser/Extension/DockerComposeControllerTest.php @@ -11,7 +11,7 @@ use Exception; use lucatume\WPBrowser\Adapters\Symfony\Component\Process\Process; use lucatume\WPBrowser\Tests\Traits\ClassStubs; -use lucatume\WPBrowser\Tests\Traits\UopzFunctions; +use lucatume\WPBrowser\Traits\UopzFunctions; use lucatume\WPBrowser\Utils\Composer; use stdClass; use Symfony\Component\Yaml\Yaml; @@ -47,7 +47,7 @@ public function _before() $this->uopzSetStaticMethodReturn(Composer::class, 'binDir', $bin); // Silence output. $this->output = new Output(['verbosity' => Output::VERBOSITY_QUIET]); - $this->uopzSetMock(Output::class, $this->output); + $this->setClassMock(Output::class, $this->output); } /** @@ -117,7 +117,7 @@ public function should_not_run_any_command_if_already_running(): void { file_put_contents(DockerComposeController::getRunningFile(), 'yes'); $constructed = 0; - $this->uopzSetMock( + $this->setClassMock( Process::class, $this->makeEmptyClass(Process::class, [ '__construct' => static function (...$args) use (&$constructed) { @@ -145,7 +145,7 @@ public function should_not_run_any_command_if_already_running(): void public function should_up_stack_correctly(): void { $constructCommands = []; - $this->uopzSetMock( + $this->setClassMock( Process::class, $this->makeEmptyClass(Process::class, [ '__construct' => static function ($command, ...$args) use (&$constructCommands) { @@ -176,7 +176,7 @@ public function should_up_stack_correctly(): void */ public function should_throw_if_config_compose_file_is_not_valid_existing_file(): void { - $this->uopzSetMock(Process::class, $this->makeEmptyClass(Process::class, [])); + $this->setClassMock(Process::class, $this->makeEmptyClass(Process::class, [])); $config = ['suites' => ['end2end'], 'compose-file' => 'not-a-file.yml']; $options = []; @@ -198,7 +198,7 @@ public function should_throw_if_config_compose_file_is_not_valid_existing_file() */ public function should_throw_if_config_env_file_is_not_valid_file(): void { - $this->uopzSetMock(Process::class, $this->makeEmptyClass(Process::class, [])); + $this->setClassMock(Process::class, $this->makeEmptyClass(Process::class, [])); $config = ['suites' => ['end2end'], 'compose-file' => 'docker-compose.yml', 'env-file' => 'not-an-env-file']; $options = []; @@ -221,7 +221,7 @@ public function should_throw_if_config_env_file_is_not_valid_file(): void public function should_correctly_handle_stack_lifecycle(): void { $constructed = 0; - $this->uopzSetMock( + $this->setClassMock( Process::class, $this->makeEmptyClass(Process::class, [ '__construct' => static function () use (&$constructed) { @@ -256,7 +256,7 @@ public function should_correctly_handle_stack_lifecycle(): void */ public function should_throw_if_docker_compose_start_fails(): void { - $this->uopzSetMock( + $this->setClassMock( Process::class, $this->makeEmptyClass(Process::class, [ 'mustRun' => static function () { @@ -283,7 +283,7 @@ public function should_throw_if_docker_compose_start_fails(): void */ public function should_throw_if_running_file_cannot_be_written(): void { - $this->uopzSetMock(Process::class, $this->makeEmptyClass(Process::class, [])); + $this->setClassMock(Process::class, $this->makeEmptyClass(Process::class, [])); $config = ['suites' => ['end2end'], 'compose-file' => 'docker-compose.yml']; $options = []; @@ -293,7 +293,7 @@ public function should_throw_if_running_file_cannot_be_written(): void $this->expectException(ExtensionException::class); $this->expectExceptionMessage('Failed to write Docker Compose running file.'); - $this->uopzSetFunctionReturn('file_put_contents', false); + $this->setFunctionReturn('file_put_contents', false); $extension->onModuleInit($this->make(SuiteEvent::class, ['getSuite' => $mockSuite])); } @@ -316,7 +316,7 @@ public function should_throw_if_stack_stopping_fails(): void $this->assertFileExists(DockerComposeController::getRunningFile()); - $this->uopzSetMock( + $this->setClassMock( Process::class, $this->makeEmptyClass(Process::class, [ 'mustRun' => static function () { @@ -338,7 +338,7 @@ public function should_throw_if_stack_stopping_fails(): void */ public function should_throw_if_running_file_cannot_be_removed_while_stopping(): void { - $this->uopzSetMock( + $this->setClassMock( Process::class, $this->makeEmptyClass(Process::class, [ 'stop' => 0 @@ -355,7 +355,7 @@ public function should_throw_if_running_file_cannot_be_removed_while_stopping(): $this->assertFileExists(DockerComposeController::getRunningFile()); - $this->uopzSetFunctionReturn('unlink', false); + $this->setFunctionReturn('unlink', false); $this->expectException(ExtensionException::class); $this->expectExceptionMessage('Failed to remove Docker Compose running file.'); @@ -370,7 +370,7 @@ public function should_throw_if_running_file_cannot_be_removed_while_stopping(): */ public function should_produce_information_correctly(): void { - $this->uopzSetMock( + $this->setClassMock( Process::class, $this->makeEmptyClass(Process::class, [ 'getOutput' => static function () { diff --git a/tests/unit/lucatume/WPBrowser/Extension/EventDispatcherBridgeTest.php b/tests/unit/lucatume/WPBrowser/Extension/EventDispatcherBridgeTest.php index 33413e4a1..59a413fc2 100644 --- a/tests/unit/lucatume/WPBrowser/Extension/EventDispatcherBridgeTest.php +++ b/tests/unit/lucatume/WPBrowser/Extension/EventDispatcherBridgeTest.php @@ -10,7 +10,7 @@ use Codeception\Test\Unit; use lucatume\WPBrowser\Events\Dispatcher; use lucatume\WPBrowser\Extension\EventDispatcherBridge; -use lucatume\WPBrowser\Tests\Traits\UopzFunctions; +use lucatume\WPBrowser\Traits\UopzFunctions; use PHPUnit\Framework\Assert; use Psr\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventDispatcher; @@ -27,7 +27,7 @@ class EventDispatcherBridgeTest extends Unit public function should_throw_if_event_dispatcher_cannot_be_found_in_trace(): void { $mockTrace = []; - $this->uopzSetFunctionReturn('debug_backtrace', $mockTrace); + $this->setFunctionReturn('debug_backtrace', $mockTrace); $eventDispatcherBridge = new EventDispatcherBridge([], []); @@ -49,7 +49,7 @@ public function should_set_global_event_dispatcher_to_application_one(): void $mockTrace = [ ['object' => $eventDispatcher] ]; - $this->uopzSetFunctionReturn('debug_backtrace', $mockTrace); + $this->setFunctionReturn('debug_backtrace', $mockTrace); $this->uopzSetStaticMethodReturn(Dispatcher::class, 'getEventDispatcher', null); $this->uopzSetStaticMethodReturn(Dispatcher::class, 'setEventDispatcher', @@ -85,7 +85,7 @@ public function should_immediately_call_previous_event_dispatcher_listeners_on_t $mockTrace = [ ['object' => $eventDispatcher] ]; - $this->uopzSetFunctionReturn('debug_backtrace', $mockTrace); + $this->setFunctionReturn('debug_backtrace', $mockTrace); $this->uopzSetStaticMethodReturn(Dispatcher::class, 'getEventDispatcher', $previousEventDispatcher); $this->uopzSetStaticMethodReturn(Dispatcher::class, 'setEventDispatcher', null); @@ -107,7 +107,7 @@ public function should_correctly_handle_the_case_where_the_previous_event_dispat $mockTrace = [ ['object' => $eventDispatcher] ]; - $this->uopzSetFunctionReturn('debug_backtrace', $mockTrace); + $this->setFunctionReturn('debug_backtrace', $mockTrace); $this->uopzSetStaticMethodReturn(Dispatcher::class, 'getEventDispatcher', null); $this->uopzSetStaticMethodReturn(Dispatcher::class, 'setEventDispatcher', diff --git a/tests/unit/lucatume/WPBrowser/ManagedProcess/ChromedriverTest.php b/tests/unit/lucatume/WPBrowser/ManagedProcess/ChromedriverTest.php index f6cdc5bfd..48a84ec7f 100644 --- a/tests/unit/lucatume/WPBrowser/ManagedProcess/ChromedriverTest.php +++ b/tests/unit/lucatume/WPBrowser/ManagedProcess/ChromedriverTest.php @@ -6,7 +6,7 @@ use Codeception\Test\Unit; use lucatume\WPBrowser\Adapters\Symfony\Component\Process\Process; use lucatume\WPBrowser\Exceptions\RuntimeException; -use lucatume\WPBrowser\Tests\Traits\UopzFunctions; +use lucatume\WPBrowser\Traits\UopzFunctions; use lucatume\WPBrowser\Utils\Composer; class ChromedriverTest extends Unit @@ -94,7 +94,7 @@ public function should_throw_if_pid_is_not_integer_on_start(): void 'isRunning' => true, 'stop' => 5 ]); - $this->uopzSetMock(Process::class, $mockProcess); + $this->setClassMock(Process::class, $mockProcess); $chromedriver = new ChromeDriver(3456, ['--url-base=wd/hub', '--headless']); @@ -116,14 +116,14 @@ public function should_throw_if_pif_file_cannot_be_written_on_start(): void 'isRunning' => true, 'getPid' => 2389, ]); - $this->uopzSetMock(Process::class, $mockProcess); + $this->setClassMock(Process::class, $mockProcess); $chromedriver = new ChromeDriver(3456, ['--url-base=wd/hub', '--headless']); $this->expectException(RuntimeException::class); $this->expectExceptionCode(ManagedProcessInterface::ERR_PID_FILE); - $this->uopzSetFunctionReturn('file_put_contents', function (string $file): false|int { + $this->setFunctionReturn('file_put_contents', function (string $file): false|int { return $file === ChromeDriver::getPidFile() ? false : 0; }, true); @@ -167,7 +167,7 @@ public function should_throw_if_pid_file_removal_fails(): void $chromedriver = new ChromeDriver(3456, ['--url-base=wd/hub', '--headless']); $chromedriver->start(); - $this->uopzSetFunctionReturn('unlink', false); + $this->setFunctionReturn('unlink', false); $this->expectException(RuntimeException::class); $this->expectExceptionCode(ManagedProcessInterface::ERR_PID_FILE_DELETE); diff --git a/tests/unit/lucatume/WPBrowser/ManagedProcess/PhpBuiltInServerTest.php b/tests/unit/lucatume/WPBrowser/ManagedProcess/PhpBuiltInServerTest.php index d8d7157d3..8c10b7d51 100644 --- a/tests/unit/lucatume/WPBrowser/ManagedProcess/PhpBuiltInServerTest.php +++ b/tests/unit/lucatume/WPBrowser/ManagedProcess/PhpBuiltInServerTest.php @@ -7,7 +7,7 @@ use lucatume\WPBrowser\Adapters\Symfony\Component\Process\Process; use lucatume\WPBrowser\Exceptions\RuntimeException; use lucatume\WPBrowser\Tests\Traits\TmpFilesCleanup; -use lucatume\WPBrowser\Tests\Traits\UopzFunctions; +use lucatume\WPBrowser\Traits\UopzFunctions; use lucatume\WPBrowser\Utils\Random; class PhpBuiltinServerProcessMock extends Process @@ -111,7 +111,7 @@ public function should_throw_if_env_is_not_associative_array(mixed $env): void public function should_start_php_built_in_server_with_specified_workers(): void { $port = Random::openLocalhostPort(); - $this->uopzSetMock(Process::class, PhpBuiltinServerProcessMock::class); + $this->setClassMock(Process::class, PhpBuiltinServerProcessMock::class); $server = new PhpBuiltInServer(__DIR__, $port, [ 'PHP_CLI_SERVER_WORKERS' => 3, @@ -130,7 +130,7 @@ public function should_start_php_built_in_server_with_specified_workers(): void public function should_start_on_random_port_if_not_specified(): void { $port = Random::openLocalhostPort(); - $this->uopzSetMock(Process::class, PhpBuiltinServerProcessMock::class); + $this->setClassMock(Process::class, PhpBuiltinServerProcessMock::class); $server = new PhpBuiltInServer(__DIR__, $port); $server->start(); diff --git a/tests/unit/lucatume/WPBrowser/Process/Protocol/ControlTest.php b/tests/unit/lucatume/WPBrowser/Process/Protocol/ControlTest.php index bda2e5eeb..890f29c46 100644 --- a/tests/unit/lucatume/WPBrowser/Process/Protocol/ControlTest.php +++ b/tests/unit/lucatume/WPBrowser/Process/Protocol/ControlTest.php @@ -7,7 +7,7 @@ use Codeception\Test\Unit; use lucatume\WPBrowser\Process\Protocol\Control; use lucatume\WPBrowser\Process\Protocol\ProtocolException; -use lucatume\WPBrowser\Tests\Traits\UopzFunctions; +use lucatume\WPBrowser\Traits\UopzFunctions; class ControlTest extends Unit { @@ -152,7 +152,7 @@ public function testConfigsCodeceptionIfConfigIsSet(): void public function testSetsCwdIfCodeceptionRootDirSet(): void { $changedDir = null; - $this->uopzSetFunctionReturn('chdir', function (string $dir) use (&$changedDir) { + $this->setFunctionReturn('chdir', function (string $dir) use (&$changedDir) { $changedDir = $changedDir ? chdir($dir) : $dir; }, true); $configCalled = false; @@ -192,7 +192,7 @@ public function testThrowsIfCodeceptionRootDirDoesNotExist(): void public function testSetsCwdIfSet(): void { $changedDir = null; - $this->uopzSetFunctionReturn('chdir', function (string $dir) use (&$changedDir) { + $this->setFunctionReturn('chdir', function (string $dir) use (&$changedDir) { $changedDir = $dir; }, true); $control = new Control([ diff --git a/tests/unit/lucatume/WPBrowser/Process/Protocol/ResponseTest.php b/tests/unit/lucatume/WPBrowser/Process/Protocol/ResponseTest.php index c128025a6..373cacee1 100644 --- a/tests/unit/lucatume/WPBrowser/Process/Protocol/ResponseTest.php +++ b/tests/unit/lucatume/WPBrowser/Process/Protocol/ResponseTest.php @@ -2,11 +2,10 @@ namespace lucatume\WPBrowser\Process\Protocol; -use CompileError; use Exception; -use lucatume\WPBrowser\Process\SerializableThrowable; -use lucatume\WPBrowser\Tests\Traits\UopzFunctions; use lucatume\WPBrowser\Opis\Closure\SerializableClosure; +use lucatume\WPBrowser\Process\SerializableThrowable; +use lucatume\WPBrowser\Traits\UopzFunctions; use PHPUnit\Framework\TestCase; use Throwable; @@ -77,7 +76,7 @@ public function testGetPayload(): void $returnValue = "success"; $exitValue = 0; $telemetry = ["memoryPeakUsage" => 123456]; - $this->uopzSetFunctionReturn('memory_get_peak_usage', 123456); + $this->setFunctionReturn('memory_get_peak_usage', 123456); $response = new Response($returnValue, $exitValue, $telemetry); diff --git a/tests/unit/lucatume/WPBrowser/Project/PluginProjectTest.php b/tests/unit/lucatume/WPBrowser/Project/PluginProjectTest.php index b86e52721..e86e8d1db 100644 --- a/tests/unit/lucatume/WPBrowser/Project/PluginProjectTest.php +++ b/tests/unit/lucatume/WPBrowser/Project/PluginProjectTest.php @@ -7,7 +7,7 @@ use lucatume\WPBrowser\Exceptions\InvalidArgumentException; use lucatume\WPBrowser\Tests\Traits\CliCommandTestingTools; use lucatume\WPBrowser\Tests\Traits\TmpFilesCleanup; -use lucatume\WPBrowser\Tests\Traits\UopzFunctions; +use lucatume\WPBrowser\Traits\UopzFunctions; use lucatume\WPBrowser\Utils\Env; use lucatume\WPBrowser\Utils\Filesystem as FS; use lucatume\WPBrowser\Utils\Random; diff --git a/tests/unit/lucatume/WPBrowser/Project/ThemeProjectTest.php b/tests/unit/lucatume/WPBrowser/Project/ThemeProjectTest.php index 5a44e6719..8e4bb4894 100644 --- a/tests/unit/lucatume/WPBrowser/Project/ThemeProjectTest.php +++ b/tests/unit/lucatume/WPBrowser/Project/ThemeProjectTest.php @@ -7,7 +7,7 @@ use lucatume\WPBrowser\Project\ThemeProject; use lucatume\WPBrowser\Tests\Traits\CliCommandTestingTools; use lucatume\WPBrowser\Tests\Traits\TmpFilesCleanup; -use lucatume\WPBrowser\Tests\Traits\UopzFunctions; +use lucatume\WPBrowser\Traits\UopzFunctions; use lucatume\WPBrowser\Utils\Env; use lucatume\WPBrowser\Utils\Filesystem as FS; use lucatume\WPBrowser\Utils\Random; diff --git a/tests/unit/lucatume/WPBrowser/Traits/UopzFunctionsTest.php b/tests/unit/lucatume/WPBrowser/Traits/UopzFunctionsTest.php new file mode 100644 index 000000000..6c58766c0 --- /dev/null +++ b/tests/unit/lucatume/WPBrowser/Traits/UopzFunctionsTest.php @@ -0,0 +1,1253 @@ +setFunctionReturn('someTestFunction', 23); + + $this->assertEquals(23, someTestFunction()); + } + + /** + * It should allow setting the return value of a namespaced function + * + * @test + */ + public function should_allow_setting_the_return_value_of_a_namespaced_function(): void + { + $this->setFunctionReturn('lucatume\WPBrowser\Acme\Project\testFunction', 23); + + $this->assertEquals(23, \lucatume\WPBrowser\Acme\Project\testFunction()); + } + + /** + * It should allow setting the return value of a function with a closure + * + * @test + */ + public function should_allow_setting_the_return_value_of_a_function_with_a_closure(): void + { + $this->setFunctionReturn('someTestFunction', static function () { + return 23; + }, true); + $this->setFunctionReturn('lucatume\WPBrowser\Acme\Project\testFunction', static function () { + return 89; + }, true); + + $this->assertEquals(23, someTestFunction()); + $this->assertEquals(89, \lucatume\WPBrowser\Acme\Project\testFunction()); + } + + /** + * It should allow setting the return value of a function to a closure value + * + * @test + */ + public function should_allow_setting_the_return_value_of_a_function_to_a_closure_value(): void + { + $closure1 = static function () { + return 23; + }; + $this->setFunctionReturn('someTestFunction', $closure1); + $closure2 = static function () { + return 89; + }; + $this->setFunctionReturn('lucatume\WPBrowser\Acme\Project\testFunction', $closure2); + + $this->assertEquals($closure1, someTestFunction()); + $this->assertEquals($closure2, \lucatume\WPBrowser\Acme\Project\testFunction()); + } + + /** + * It should handle functions with reference arguments + * + * @test + */ + public function should_handle_functions_with_reference_arguments(): void + { + $this->setFunctionReturn('someReferenceFunction', null); + + $input = [23, 89]; + someReferenceFunction($input); + $this->assertEquals([23, 89], $input); + + $this->setFunctionReturn('someReferenceFunction', function (array &$input) { + $input[] = 'hello'; + }, true); + + someReferenceFunction($input); + + $this->assertEquals([23, 89, 'hello'], $input); + + $this->setFunctionReturn('lucatume\WPBrowser\Acme\Project\someReferenceFunction', null); + + $input = [23, 89]; + \lucatume\WPBrowser\Acme\Project\someReferenceFunction($input); + $this->assertEquals([23, 89], $input); + + $this->setFunctionReturn('lucatume\WPBrowser\Acme\Project\someReferenceFunction', function (array &$input) { + $input[] = 'hello'; + }, true); + + \lucatume\WPBrowser\Acme\Project\someReferenceFunction($input); + + $this->assertEquals([23, 89, 'hello'], $input); + } + + /** + * It should unset function return value between tests + * + * @test + */ + public function should_unset_function_return_value_between_tests(): void + { + $this->assertEquals('test-test-test', someTestFunction()); + $this->assertEquals('test-test-test', \lucatume\WPBrowser\Acme\Project\testFunction()); + } + + /** + * It should allow unsetting a function return value + * + * @test + */ + public function should_allow_unsetting_a_function_return_value(): void + { + $this->setFunctionReturn('someTestFunction', 23); + $this->setFunctionReturn('lucatume\WPBrowser\Acme\Project\testFunction', 89); + + $this->assertEquals(23, someTestFunction()); + $this->assertEquals(89, \lucatume\WPBrowser\Acme\Project\testFunction()); + + $this->unsetFunctionReturn('someTestFunction'); + $this->unsetFunctionReturn('lucatume\WPBrowser\Acme\Project\testFunction'); + + $this->assertEquals('test-test-test', someTestFunction()); + $this->assertEquals('test-test-test', \lucatume\WPBrowser\Acme\Project\testFunction()); + } + + /** + * It should not throw when unsetting a non-set function return value + * + * @test + */ + public function should_not_throw_when_unsetting_a_non_set_function_return_value(): void + { + $this->unsetFunctionReturn('someTestFunction'); + $this->unsetFunctionReturn('Acme\Project\testFunction'); + } + + /** + * It should allow setting an instance method return value + * + * @test + */ + public function should_allow_setting_an_instance_method_return_value(): void + { + $this->setMethodReturn(SomeGlobalClassOne::class, 'getValueOne', 23); + $return23 = fn() => 23; + $this->setMethodReturn(SomeGlobalClassOne::class, 'getValueTwo', $return23); + $this->setMethodReturn(SomeGlobalClassOne::class, 'getValueThree', fn() => 89, true); + $this->setMethodReturn(SomeNamespacedClassOne::class, 'getValueOne', 23); + $return23 = fn() => 23; + $this->setMethodReturn(SomeNamespacedClassOne::class, 'getValueTwo', $return23); + $this->setMethodReturn(SomeNamespacedClassOne::class, 'getValueThree', fn() => 89, true); + + $globalClass = new SomeGlobalClassOne(); + $namespacedClass = new SomeNamespacedClassOne(); + + $this->assertEquals(23, $globalClass->getValueOne()); + $this->assertEquals($return23, $globalClass->getValueTwo()); + $this->assertEquals(89, $globalClass->getValueThree()); + $this->assertEquals(23, $namespacedClass->getValueOne()); + $this->assertEquals($return23, $namespacedClass->getValueTwo()); + $this->assertEquals(89, $namespacedClass->getValueThree()); + } + + /** + * It should allow setting a static method return value + * + * @test + */ + public function should_allow_setting_a_static_method_return_value(): void + { + $this->setMethodReturn(SomeGlobalClassOne::class, 'getStaticValueOne', 23); + $return23 = fn() => 23; + $this->setMethodReturn(SomeGlobalClassOne::class, 'getStaticValueTwo', $return23); + $this->setMethodReturn(SomeGlobalClassOne::class, 'getStaticValueThree', fn() => 89, true); + $this->setMethodReturn(SomeNamespacedClassOne::class, 'getStaticValueOne', 23); + $return23 = fn() => 23; + $this->setMethodReturn(SomeNamespacedClassOne::class, 'getStaticValueTwo', $return23); + $this->setMethodReturn(SomeNamespacedClassOne::class, 'getStaticValueThree', fn() => 89, true); + + $this->assertEquals(23, SomeGlobalClassOne::getStaticValueOne()); + $this->assertEquals($return23, SomeGlobalClassOne::getStaticValueTwo()); + $this->assertEquals(89, SomeGlobalClassOne::getStaticValueThree()); + $this->assertEquals(23, SomeNamespacedClassOne::getStaticValueOne()); + $this->assertEquals($return23, SomeNamespacedClassOne::getStaticValueTwo()); + $this->assertEquals(89, SomeNamespacedClassOne::getStaticValueThree()); + } + + /** + * It should allow unsetting a set method return value + * + * @test + */ + public function should_allow_unsetting_a_set_method_return_value(): void + { + $this->setMethodReturn(SomeGlobalClassOne::class, 'getValueOne', 23); + $this->setMethodReturn(SomeGlobalClassOne::class, 'getStaticValueOne', 23); + $this->setMethodReturn(SomeNamespacedClassOne::class, 'getValueOne', 23); + $this->setMethodReturn(SomeNamespacedClassOne::class, 'getStaticValueOne', 23); + + $this->assertEquals(23, (new SomeGlobalClassOne())->getValueOne()); + $this->assertEquals(23, SomeGlobalClassOne::getStaticValueOne()); + $this->assertEquals(23, (new SomeNamespacedClassOne())->getValueOne()); + $this->assertEquals(23, SomeNamespacedClassOne::getStaticValueOne()); + + $this->unsetMethodReturn(SomeGlobalClassOne::class, 'getValueOne'); + $this->unsetMethodReturn(SomeGlobalClassOne::class, 'getStaticValueOne'); + $this->unsetMethodReturn(SomeNamespacedClassOne::class, 'getValueOne'); + $this->unsetMethodReturn(SomeNamespacedClassOne::class, 'getStaticValueOne'); + + $this->assertEquals('original-value-one', (new SomeGlobalClassOne())->getValueOne()); + $this->assertEquals('original-static-value-one', SomeGlobalClassOne::getStaticValueOne()); + $this->assertEquals('original-value-one', (new SomeNamespacedClassOne())->getValueOne()); + $this->assertEquals('original-static-value-one', SomeNamespacedClassOne::getStaticValueOne()); + } + + /** + * It should not throw when unsetting a non-set method return value + * + * @test + */ + public function should_not_throw_when_unsetting_a_non_set_method_return_value(): void + { + $this->unsetMethodReturn(SomeGlobalClassOne::class, 'getValueOne'); + $this->unsetMethodReturn(SomeGlobalClassOne::class, 'getStaticValueOne'); + $this->unsetMethodReturn(SomeNamespacedClassOne::class, 'getValueOne'); + $this->unsetMethodReturn(SomeNamespacedClassOne::class, 'getStaticValueOne'); + } + + /** + * It should handle method with reference arguments + * + * @test + */ + public function should_handle_method_with_reference_arguments(): void + { + $this->setMethodReturn(SomeGlobalClassOne::class, 'modifyValueByReference', null); + + $input = [23, 89]; + $globalClass = new SomeGlobalClassOne(); + $globalClass->modifyValueByReference($input); + $this->assertEquals([23, 89], $input); + + $this->setMethodReturn(SomeGlobalClassOne::class, 'modifyStaticValueByReference', null); + + $input = [23, 89]; + SomeGlobalClassOne::modifyStaticValueByReference($input); + $this->assertEquals([23, 89], $input); + + $this->setMethodReturn(SomeNamespacedClassOne::class, 'modifyValueByReference', null); + + $input = [23, 89]; + $namespacedClass = new SomeNamespacedClassOne(); + $namespacedClass->modifyValueByReference($input); + $this->assertEquals([23, 89], $input); + + $this->setMethodReturn(SomeNamespacedClassOne::class, 'modifyStaticValueByReference', null); + + $input = [23, 89]; + SomeNamespacedClassOne::modifyStaticValueByReference($input); + $this->assertEquals([23, 89], $input); + + $this->setMethodReturn(SomeGlobalClassOne::class, 'modifyValueByReference', function (array &$input) { + $input[] = 'hello'; + }, true); + + $input = [23, 89]; + $globalClass->modifyValueByReference($input); + $this->assertEquals([23, 89, 'hello'], $input); + + $this->setMethodReturn(SomeGlobalClassOne::class, 'modifyStaticValueByReference', function (array &$input) { + $input[] = 'hello'; + }, true); + + $input = [23, 89]; + SomeGlobalClassOne::modifyStaticValueByReference($input); + $this->assertEquals([23, 89, 'hello'], $input); + + $this->setMethodReturn(SomeNamespacedClassOne::class, 'modifyValueByReference', function (array &$input) { + $input[] = 'hello'; + }, true); + + $input = [23, 89]; + $namespacedClass->modifyValueByReference($input); + $this->assertEquals([23, 89, 'hello'], $input); + + $this->setMethodReturn(SomeNamespacedClassOne::class, 'modifyStaticValueByReference', function (array &$input) { + $input[] = 'hello'; + }, true); + + $input = [23, 89]; + SomeNamespacedClassOne::modifyStaticValueByReference($input); + $this->assertEquals([23, 89, 'hello'], $input); + } + + /** + * It should allow setting a function hook + * + * @test + */ + public function should_allow_setting_a_function_hook(): void + { + $headers = []; + $hook = function (string $header, bool $replace = true, int $response_code = 0) use ( + &$headers + ): void { + $headers[] = [ + 'header' => $header, + 'replace' => $replace, + 'response_code' => $response_code, + ]; + }; + + $headers = []; + $this->setFunctionHook('header', $hook); + + header('Location: http://example.com', true, 301); + header('X-Frame-Options: DENY', false, 200); + + $this->assertEquals([ + [ + 'header' => 'Location: http://example.com', + 'replace' => true, + 'response_code' => 301, + ], + [ + 'header' => 'X-Frame-Options: DENY', + 'replace' => false, + 'response_code' => 200, + ], + ], $headers); + + $headers = []; + $this->setFunctionHook('someHeaderProxy', $hook); + + someHeaderProxy('X-REST-URL: http://example.com'); + + $this->assertEquals([ + [ + 'header' => 'X-REST-URL: http://example.com', + 'replace' => true, + 'response_code' => 0, + ], + [ + 'header' => 'X-REST-URL: http://example.com', + 'replace' => true, + 'response_code' => 0, + ], + ], $headers); + + $headers = []; + $this->setFunctionHook('lucatume\WPBrowser\Acme\Project\someHeaderProxy', $hook); + + \lucatume\WPBrowser\Acme\Project\someHeaderProxy('X-REST-URL: http://example.com'); + + $this->assertEquals([ + [ + 'header' => 'X-REST-URL: http://example.com', + 'replace' => true, + 'response_code' => 0, + ], + [ + 'header' => 'X-REST-URL: http://example.com', + 'replace' => true, + 'response_code' => 0, + ], + ], $headers); + } + + /** + * It should allow unsetting a function hook + * + * @test + */ + public function should_allow_unsetting_a_function_hook(): void + { + $headers = []; + $hook = function (string $header, bool $replace = true, int $response_code = 0) use ( + &$headers + ): void { + $headers[] = [ + 'header' => $header, + 'replace' => $replace, + 'response_code' => $response_code, + ]; + }; + + $this->setFunctionHook('header', $hook); + $this->setFunctionHook('someHeaderProxy', $hook); + $this->setFunctionHook('lucatume\WPBrowser\Acme\Project\someHeaderProxy', $hook); + + header('Location: http://example.com', true, 301); + header('X-Frame-Options: DENY', false, 200); + someHeaderProxy('X-REST-URL: http://example.com'); + \lucatume\WPBrowser\Acme\Project\someHeaderProxy('X-REST-URL: http://example.com'); + + $this->assertCount(6, $headers); + + $this->unsetFunctionHook('header'); + $this->unsetFunctionHook('someHeaderProxy'); + $this->unsetFunctionHook('lucatume\WPBrowser\Acme\Project\someHeaderProxy'); + $headers = []; + + header('Location: http://example.com', true, 301); + header('X-Frame-Options: DENY', false, 200); + someHeaderProxy('X-REST-URL: http://example.com'); + \lucatume\WPBrowser\Acme\Project\someHeaderProxy('X-REST-URL: http://example.com'); + + $this->assertCount(0, $headers); + } + + /** + * It should not throw when unsetting a not set function hook + * + * @test + */ + public function should_not_throw_when_unsetting_a_not_set_function_hook(): void + { + $this->unsetFunctionHook('header'); + $this->unsetFunctionHook('someHeaderProxy'); + $this->unsetFunctionHook('lucatume\WPBrowser\Acme\Project\someHeaderProxy'); + } + + /** + * It should allow setting a method hook + * + * @test + */ + public function should_allow_setting_a_method_hook(): void + { + $headers = []; + $hook = function (string $header, bool $replace = true, int $response_code = 0) use ( + &$headers + ): void { + $headers[] = [ + 'header' => $header, + 'replace' => $replace, + 'response_code' => $response_code, + ]; + }; + + $this->setMethodHook(SomeGlobalClassOne::class, 'someHeaderProxy', $hook); + $this->setMethodHook(SomeGlobalClassOne::class, 'someStaticHeaderProxy', $hook); + $this->setMethodHook(SomeNamespacedClassOne::class, 'someHeaderProxy', $hook); + $this->setMethodHook(SomeNamespacedClassOne::class, 'someStaticHeaderProxy', $hook); + + $globalClass = new SomeGlobalClassOne(); + $namespacedClass = new SomeNamespacedClassOne(); + + $headers = []; + $globalClass->someHeaderProxy('X-REST-URL: http://example.com'); + + $this->assertEquals([ + [ + 'header' => 'X-REST-URL: http://example.com', + 'replace' => true, + 'response_code' => 0, + ] + ], $headers); + + $headers = []; + SomeGlobalClassOne::someStaticHeaderProxy('X-REST-URL: http://example.com'); + + $this->assertEquals([ + [ + 'header' => 'X-REST-URL: http://example.com', + 'replace' => true, + 'response_code' => 0, + ] + ], $headers); + + $headers = []; + $namespacedClass->someHeaderProxy('X-REST-URL: http://example.com'); + + $this->assertEquals([ + [ + 'header' => 'X-REST-URL: http://example.com', + 'replace' => true, + 'response_code' => 0, + ] + ], $headers); + + $headers = []; + SomeNamespacedClassOne::someStaticHeaderProxy('X-REST-URL: http://example.com'); + + $this->assertEquals([ + [ + 'header' => 'X-REST-URL: http://example.com', + 'replace' => true, + 'response_code' => 0, + ] + ], $headers); + + $headers = []; + + $this->unsetMethodHook(SomeGlobalClassOne::class, 'someHeaderProxy'); + $this->unsetMethodHook(SomeGlobalClassOne::class, 'someStaticHeaderProxy'); + $this->unsetMethodHook(SomeNamespacedClassOne::class, 'someHeaderProxy'); + $this->unsetMethodHook(SomeNamespacedClassOne::class, 'someStaticHeaderProxy'); + + $globalClass->someHeaderProxy('X-REST-URL: http://example.com'); + SomeGlobalClassOne::someStaticHeaderProxy('X-REST-URL: http://example.com'); + $namespacedClass->someHeaderProxy('X-REST-URL: http://example.com'); + SomeNamespacedClassOne::someStaticHeaderProxy('X-REST-URL: http://example.com'); + + $this->assertCount(0, $headers); + } + + /** + * It should not throw if unsetting a not set method hook + * + * @test + */ + public function should_not_throw_if_unsetting_a_not_set_method_hook(): void + { + $this->unsetMethodHook(SomeGlobalClassOne::class, 'someHeaderProxy'); + $this->unsetMethodHook(SomeGlobalClassOne::class, 'someStaticHeaderProxy'); + $this->unsetMethodHook(SomeNamespacedClassOne::class, 'someHeaderProxy'); + $this->unsetMethodHook(SomeNamespacedClassOne::class, 'someStaticHeaderProxy'); + } + + /** + * It should allow setting a constant + * + * @test + */ + public function should_allow_setting_a_constant(): void + { + $this->setConstant('EXISTING_CONSTANT', 23); + $this->setConstant('NOT_EXISTING_CONSTANT', 89); + $this->setConstant('lucatume\WPBrowser\Acme\Project\EXISTING_CONSTANT', 89); + $this->setConstant('lucatume\WPBrowser\Acme\Project\NOT_EXISTING_CONSTANT', 23); + + $this->assertEquals(23, EXISTING_CONSTANT); + $this->assertEquals(89, \NOT_EXISTING_CONSTANT); + $this->assertEquals(89, \lucatume\WPBrowser\Acme\Project\EXISTING_CONSTANT); + $this->assertEquals(23, NOT_EXISTING_CONSTANT); + + $this->unsetConstant('EXISTING_CONSTANT'); + $this->unsetConstant('NOT_EXISTING_CONSTANT'); + $this->unsetConstant('lucatume\WPBrowser\Acme\Project\EXISTING_CONSTANT'); + $this->unsetConstant('lucatume\WPBrowser\Acme\Project\NOT_EXISTING_CONSTANT'); + + $this->assertEquals('test-constant', EXISTING_CONSTANT); + $this->assertFalse(defined('NOT_EXISTING_CONSTANT')); + $this->assertEquals('test-constant', \lucatume\WPBrowser\Acme\Project\EXISTING_CONSTANT); + $this->assertFalse(defined('\lucatume\WPBrowser\Acme\Project\NOT_EXISTING_CONSTANT')); + } + + /** + * It should allow setting a class constant + * + * @test + */ + public function should_allow_setting_a_class_constant(): void + { + $this->setClassConstant('SomeGlobalClassOne', 'EXISTING_CONSTANT', 23); + $this->setClassConstant('SomeGlobalClassOne', 'NOT_EXISTING_CONSTANT', 89); + $this->setClassConstant('lucautme\WPBrowser\Acme\Project\SomeNamespacedClassOne', 'EXISTING_CONSTANT', 89); + $this->setClassConstant('lucautme\WPBrowser\Acme\Project\SomeNamespacedClassOne', 'NOT_EXISTING_CONSTANT', 23); + + $this->assertEquals(23, SomeGlobalClassOne::EXISTING_CONSTANT); + $this->assertEquals(89, SomeGlobalClassOne::NOT_EXISTING_CONSTANT); + $this->assertEquals(89, SomeNamespacedClassOne::EXISTING_CONSTANT); + $this->assertEquals(23, SomeNamespacedClassOne::NOT_EXISTING_CONSTANT); + + $this->unsetClassConstant('SomeGlobalClassOne', 'EXISTING_CONSTANT'); + $this->unsetClassConstant('SomeGlobalClassOne', 'NOT_EXISTING_CONSTANT'); + $this->unsetClassConstant('lucautme\WPBrowser\Acme\Project\SomeNamespacedClassOne', 'EXISTING_CONSTANT'); + $this->unsetClassConstant('lucautme\WPBrowser\Acme\Project\SomeNamespacedClassOne', 'NOT_EXISTING_CONSTANT'); + + $this->assertEquals('test-constant', SomeGlobalClassOne::EXISTING_CONSTANT); + $this->assertFalse(defined('SomeGlobalClassOne::NOT_EXISTING_CONSTANT')); + $this->assertEquals( + 'test-constant', + SomeNamespacedClassOne::EXISTING_CONSTANT + ); + $this->assertFalse(defined('\lucautme\WPBrowser\Acme\Project\SomeNamespacedClassOne::NOT_EXISTING_CONSTANT')); + } + + /** + * It should allow setting a class mock to instance + * + * @test + */ + public function should_allow_setting_a_class_mock_to_instance(): void + { + $mockSomeGlobalClassOne = new class extends SomeGlobalClassOne { + public function getValueOne(): string + { + return 'mocked-value-one'; + } + }; + $this->setClassMock(SomeGlobalClassOne::class, $mockSomeGlobalClassOne); + + $mockSomeGlobalClassOneInstanceOne = new SomeGlobalClassOne(); + $mockSomeGlobalClassOneInstanceTwo = new SomeGlobalClassOne(); + + $this->assertSame($mockSomeGlobalClassOne, $mockSomeGlobalClassOneInstanceOne); + $this->assertSame($mockSomeGlobalClassOne, $mockSomeGlobalClassOneInstanceTwo); + $this->assertEquals('mocked-value-one', $mockSomeGlobalClassOneInstanceOne->getValueOne()); + $this->assertEquals('mocked-value-one', $mockSomeGlobalClassOneInstanceTwo->getValueOne()); + + $mockSomeNamespacedClassOne = new class extends SomeNamespacedClassOne { + public function getValueOne(): string + { + return 'mocked-value-one'; + } + }; + $this->setClassMock( + SomeNamespacedClassOne::class, + $mockSomeNamespacedClassOne + ); + + $mockSomeNamespacedClassOneInstanceOne = new SomeNamespacedClassOne(); + $mockSomeNamespacedClassOneInstanceTwo = new SomeNamespacedClassOne(); + $this->assertEquals('mocked-value-one', $mockSomeNamespacedClassOneInstanceOne->getValueOne()); + $this->assertEquals('mocked-value-one', $mockSomeNamespacedClassOneInstanceTwo->getValueOne()); + + $this->unsetClassMock(SomeGlobalClassOne::class); + $this->unsetClassMock(SomeNamespacedClassOne::class); + + $this->assertNotSame($mockSomeGlobalClassOne, new SomeGlobalClassOne()); + $this->assertNotSame($mockSomeNamespacedClassOne, new SomeNamespacedClassOne()); + } + + /** + * It should allow setting a class mock to a class + * + * @test + */ + public function should_allow_setting_a_class_mock_to_a_class(): void + { + $this->setClassMock(SomeGlobalClassOne::class, SomeGlobalClassTwo::class); + + $mockSomeGlobalClassOneInstanceOne = new SomeGlobalClassOne(); + $mockSomeGlobalClassOneInstanceTwo = new SomeGlobalClassOne(); + + $this->assertInstanceOf(SomeGlobalClassTwo::class, $mockSomeGlobalClassOneInstanceOne); + $this->assertInstanceOf(SomeGlobalClassTwo::class, $mockSomeGlobalClassOneInstanceTwo); + $this->assertNotSame($mockSomeGlobalClassOneInstanceOne, $mockSomeGlobalClassOneInstanceTwo); + $this->assertEquals('another-value', $mockSomeGlobalClassOneInstanceOne->getValueOne()); + $this->assertEquals('another-value', $mockSomeGlobalClassOneInstanceTwo->getValueOne()); + + $this->setClassMock(SomeNamespacedClassOne::class, SomeNamespacedClassTwo::class); + + $mockSomeNamespacedClassOneInstanceOne = new SomeNamespacedClassOne(); + $mockSomeNamespacedClassOneInstanceTwo = new SomeNamespacedClassOne(); + + $this->assertInstanceOf(SomeNamespacedClassTwo::class, $mockSomeNamespacedClassOneInstanceOne); + $this->assertInstanceOf(SomeNamespacedClassTwo::class, $mockSomeNamespacedClassOneInstanceTwo); + $this->assertNotSame($mockSomeNamespacedClassOneInstanceOne, $mockSomeNamespacedClassOneInstanceTwo); + $this->assertEquals('another-value', $mockSomeNamespacedClassOneInstanceOne->getValueOne()); + $this->assertEquals('another-value', $mockSomeNamespacedClassOneInstanceTwo->getValueOne()); + + $this->unsetClassMock(SomeGlobalClassOne::class); + $this->unsetClassMock(SomeNamespacedClassOne::class); + + $this->assertInstanceOf(SomeGlobalClassOne::class, new SomeGlobalClassOne()); + $this->assertInstanceOf(SomeNamespacedClassOne::class, new SomeNamespacedClassOne()); + } + + /** + * It should not throw if trying to unset a not set class mock + * + * @test + */ + public function should_not_throw_if_trying_to_unset_a_not_set_class_mock(): void + { + $this->unsetClassMock(SomeGlobalClassOne::class); + $this->unsetClassMock(SomeNamespacedClassOne::class); + } + + /** + * It should allow unsetting a class final attribute + * + * @test + */ + public function should_allow_unsetting_a_class_final_attribute(): void + { + $this->unsetClassFinalAttribute(SomeGlobalFinalClass::class); + $this->unsetClassFinalAttribute(SomeNamespacedFinalClass::class); + + $globalExtension = new class extends SomeGlobalFinalClass { + public function someMethod(): int + { + return 89; + } + }; + $this->assertEquals(89, $globalExtension->someMethod()); + + $namespacedExtension = new class extends SomeNamespacedFinalClass { + public function someMethod(): int + { + return 89; + } + }; + $this->assertEquals(89, $namespacedExtension->someMethod()); + + $this->resetClassFinalAttribute(SomeGlobalFinalClass::class); + $this->resetClassFinalAttribute(SomeNamespacedFinalClass::class); + + $this->assertTrue((new ReflectionClass(SomeGlobalFinalClass::class))->isFinal()); + $this->assertTrue((new ReflectionClass(SomeNamespacedFinalClass::class))->isFinal()); + } + + /** + * It should not throw if trying to reset a not set class final attribute + * + * @test + */ + public function should_not_throw_if_trying_to_reset_a_not_set_class_final_attribute(): void + { + $this->resetClassFinalAttribute(SomeGlobalFinalClass::class); + $this->resetClassFinalAttribute(SomeNamespacedFinalClass::class); + } + + /** + * It should allow unsetting a class method final attribute + * + * @test + */ + public function should_allow_unsetting_a_class_method_final_attribute(): void + { + $this->unsetMethodFinalAttribute(SomeGlobalClassWithFinalMethods::class, 'someFinalMethod'); + $this->unsetMethodFinalAttribute(SomeGlobalClassWithFinalMethods::class, 'someStaticFinalMethod'); + $this->unsetMethodFinalAttribute(SomeNamespacedClassWithFinalMethods::class, 'someFinalMethod'); + $this->unsetMethodFinalAttribute(SomeNamespacedClassWithFinalMethods::class, 'someStaticFinalMethod'); + + $globalExtension = new class extends SomeGlobalClassWithFinalMethods { + public function someFinalMethod(): int + { + return 123; + } + + public static function someStaticFinalMethod(): int + { + return 189; + } + }; + + $this->assertEquals(123, $globalExtension->someFinalMethod()); + $this->assertEquals(189, $globalExtension::someStaticFinalMethod()); + + $namespacedExtension = new class extends SomeNamespacedClassWithFinalMethods { + public function someFinalMethod(): int + { + return 91; + } + + public static function someStaticFinalMethod(): int + { + return 66; + } + }; + + $this->assertEquals(91, $namespacedExtension->someFinalMethod()); + $this->assertEquals(66, $namespacedExtension::someStaticFinalMethod()); + + $this->resetMethodFinalAttribute(SomeGlobalClassWithFinalMethods::class, 'someFinalMethod'); + $this->resetMethodFinalAttribute(SomeGlobalClassWithFinalMethods::class, 'someStaticFinalMethod'); + $this->resetMethodFinalAttribute(SomeNamespacedClassWithFinalMethods::class, 'someFinalMethod'); + $this->resetMethodFinalAttribute(SomeNamespacedClassWithFinalMethods::class, 'someStaticFinalMethod'); + + $this->assertTrue( + (new ReflectionMethod(SomeGlobalClassWithFinalMethods::class, 'someFinalMethod'))->isFinal() + ); + $this->assertTrue( + (new ReflectionMethod(SomeGlobalClassWithFinalMethods::class, 'someStaticFinalMethod'))->isFinal() + ); + $this->assertTrue( + (new ReflectionMethod(SomeNamespacedClassWithFinalMethods::class, 'someFinalMethod'))->isFinal() + ); + $this->assertTrue( + (new ReflectionMethod(SomeNamespacedClassWithFinalMethods::class, 'someStaticFinalMethod'))->isFinal() + ); + } + + /** + * It should not throw if trying to reset a not unset method final attribute + * + * @test + */ + public function should_not_throw_if_trying_to_reset_a_not_unset_method_final_attribute(): void + { + $this->resetMethodFinalAttribute(SomeGlobalClassWithFinalMethods::class, 'someFinalMethod'); + $this->resetMethodFinalAttribute(SomeGlobalClassWithFinalMethods::class, 'someStaticFinalMethod'); + $this->resetMethodFinalAttribute(SomeNamespacedClassWithFinalMethods::class, 'someFinalMethod'); + $this->resetMethodFinalAttribute(SomeNamespacedClassWithFinalMethods::class, 'someStaticFinalMethod'); + } + + /** + * It should allow adding class methods + * + * @test + */ + public function should_allow_adding_class_methods(): void + { + $this->addClassMethod(SomeGlobalClassWithoutMethods::class, 'instanceMethod', function (): int { + return $this->number; + }); + $this->addClassMethod(SomeGlobalClassWithoutMethods::class, 'staticMethod', function (): string { + return self::$name; + }, true); + + $this->assertEquals(23, (new SomeGlobalClassWithoutMethods())->instanceMethod()); + $this->assertEquals('Luca', SomeGlobalClassWithoutMethods::staticMethod()); + + $this->removeClassMethod(SomeGlobalClassWithoutMethods::class, 'instanceMethod'); + $this->removeClassMethod(SomeGlobalClassWithoutMethods::class, 'staticMethod'); + + $this->assertFalse(method_exists(SomeGlobalClassWithoutMethods::class, 'instanceMethod')); + $this->assertFalse(method_exists(SomeGlobalClassWithoutMethods::class, 'staticMethod')); + + $this->addClassMethod(SomeNamespacedClassWithoutMethods::class, 'instanceMethod', function (): int { + return $this->number; + }); + $this->addClassMethod(SomeNamespacedClassWithoutMethods::class, 'staticMethod', function (): string { + return self::$name; + }, true); + + $this->assertEquals(23, (new SomeNamespacedClassWithoutMethods())->instanceMethod()); + $this->assertEquals('Luca', SomeNamespacedClassWithoutMethods::staticMethod()); + + $this->removeClassMethod(SomeNamespacedClassWithoutMethods::class, 'instanceMethod'); + $this->removeClassMethod(SomeNamespacedClassWithoutMethods::class, 'staticMethod'); + + $this->assertFalse(method_exists(SomeNamespacedClassWithoutMethods::class, 'instanceMethod')); + $this->assertFalse(method_exists(SomeNamespacedClassWithoutMethods::class, 'staticMethod')); + } + + /** + * It should not throw if trying to remove not added class methods + * + * @test + */ + public function should_not_throw_if_trying_to_remove_not_added_class_methods(): void + { + $this->removeClassMethod(SomeGlobalClassWithoutMethods::class, 'someNonExistingInstanceMethod'); + $this->removeClassMethod(SomeGlobalClassWithoutMethods::class, 'someNonExistingStaticMethod'); + $this->removeClassMethod(SomeNamespacedClassWithoutMethods::class, 'someNonExistingInstanceMethod'); + $this->removeClassMethod(SomeNamespacedClassWithoutMethods::class, 'someNonExistingStaticMethod'); + } + + /** + * It should allow setting object properties + * + * @test + */ + public function should_allow_setting_object_properties(): void + { + $globalClassInstance = new SomeGlobalClassWithoutMethods(); + $this->setObjectProperty($globalClassInstance, 'number', 89); + $this->setObjectProperty(SomeGlobalClassWithoutMethods::class, 'name', 'Bob'); + + $this->assertEquals(89, $this->getObjectProperty($globalClassInstance, 'number')); + $this->assertEquals('Bob', $this->getObjectProperty(SomeGlobalClassWithoutMethods::class, 'name')); + + $this->resetObjectProperty($globalClassInstance, 'number'); + $this->resetObjectProperty(SomeGlobalClassWithoutMethods::class, 'name'); + $this->resetObjectProperty($globalClassInstance, 'someNonExistingInstanceProperty'); + $this->resetObjectProperty(SomeGlobalClassWithoutMethods::class, 'someNonExistingStaticProperty'); + + $this->assertEquals(23, $this->getObjectProperty($globalClassInstance, 'number')); + $this->assertEquals('Luca', $this->getObjectProperty(SomeGlobalClassWithoutMethods::class, 'name')); + + $namespacedClassInstance = new SomeNamespacedClassWithoutMethods(); + $this->setObjectProperty($namespacedClassInstance, 'number', 89); + $this->setObjectProperty(SomeNamespacedClassWithoutMethods::class, 'name', 'Bob'); + + $this->assertEquals(89, $this->getObjectProperty($namespacedClassInstance, 'number')); + $this->assertEquals('Bob', $this->getObjectProperty(SomeNamespacedClassWithoutMethods::class, 'name')); + + $this->resetObjectProperty($namespacedClassInstance, 'number'); + $this->resetObjectProperty(SomeNamespacedClassWithoutMethods::class, 'name'); + $this->resetObjectProperty($namespacedClassInstance, 'someNonExistingInstanceProperty'); + $this->resetObjectProperty(SomeNamespacedClassWithoutMethods::class, 'someNonExistingStaticProperty'); + + $this->assertEquals(23, $this->getObjectProperty($namespacedClassInstance, 'number')); + $this->assertEquals('Luca', $this->getObjectProperty(SomeNamespacedClassWithoutMethods::class, 'name')); + } + + /** + * It should not throw if trying to reset not set object properties + * + * @test + */ + public function should_not_throw_if_trying_to_reset_not_set_object_properties(): void + { + $globalClassInstance = new SomeGlobalClassWithoutMethods(); + $this->resetObjectProperty($globalClassInstance, 'someNonExistingInstanceProperty'); + $this->resetObjectProperty(SomeGlobalClassWithoutMethods::class, 'someNonExistingStaticProperty'); + + $namespacedClassInstance = new SomeNamespacedClassWithoutMethods(); + $this->resetObjectProperty($namespacedClassInstance, 'someNonExistingInstanceProperty'); + $this->resetObjectProperty(SomeNamespacedClassWithoutMethods::class, 'someNonExistingStaticProperty'); + } + + /** + * It should allow setting a method static variable + * + * @test + */ + public function should_allow_setting_a_method_static_variable(): void + { + $someGlobalClassWithStaticVariablesInstance = new SomeGlobalClassWithStaticVariables(); + + $this->setMethodStaticVariables( + SomeGlobalClassWithStaticVariables::class, + 'theCounter', + array_merge( + $this->getMethodStaticVariables(SomeGlobalClassWithStaticVariables::class, 'theCounter'), + ['counter' => 23] + ) + ); + $this->setMethodStaticVariables( + SomeGlobalClassWithStaticVariables::class, + 'theStaticCounter', + array_merge( + $this->getMethodStaticVariables(SomeGlobalClassWithStaticVariables::class, 'theStaticCounter'), + ['counter' => 89] + ) + ); + + $this->assertEquals(23, $someGlobalClassWithStaticVariablesInstance->theCounter()); + $this->assertEquals(89, SomeGlobalClassWithStaticVariables::theStaticCounter()); + + $this->resetMethodStaticVariables(SomeGlobalClassWithStaticVariables::class, 'theCounter'); + $this->resetMethodStaticVariables(SomeGlobalClassWithStaticVariables::class, 'theStaticCounter'); + + $this->assertEquals( + 0, + $this->getMethodStaticVariables(SomeGlobalClassWithStaticVariables::class, 'theCounter')['counter'] + ); + $this->assertEquals( + 0, + $this->getMethodStaticVariables(SomeGlobalClassWithStaticVariables::class, 'theStaticCounter')['counter'] + ); + + $namespacedClassWithStaticVariablesInstance = new NamespacedClassWithStaticVariables(); + + $this->setMethodStaticVariables( + NamespacedClassWithStaticVariables::class, + 'theCounter', + array_merge( + $this->getMethodStaticVariables(NamespacedClassWithStaticVariables::class, 'theCounter'), + ['counter' => 23] + ) + ); + $this->setMethodStaticVariables( + NamespacedClassWithStaticVariables::class, + 'theStaticCounter', + array_merge( + $this->getMethodStaticVariables(NamespacedClassWithStaticVariables::class, 'theStaticCounter'), + ['counter' => 89] + ) + ); + + $this->assertEquals(23, $namespacedClassWithStaticVariablesInstance->theCounter()); + $this->assertEquals(89, NamespacedClassWithStaticVariables::theStaticCounter()); + + $this->resetMethodStaticVariables(NamespacedClassWithStaticVariables::class, 'theCounter'); + $this->resetMethodStaticVariables(NamespacedClassWithStaticVariables::class, 'theStaticCounter'); + + $this->assertEquals( + 0, + $this->getMethodStaticVariables(NamespacedClassWithStaticVariables::class, 'theCounter')['counter'] + ); + $this->assertEquals( + 0, + $this->getMethodStaticVariables(NamespacedClassWithStaticVariables::class, 'theStaticCounter')['counter'] + ); + + + $this->setMethodStaticVariables( + SomeGlobalClassWithStaticVariables::class, + 'theCounter', + ['counter' => 23] + ); + $this->setMethodStaticVariables( + SomeGlobalClassWithStaticVariables::class, + 'theStaticCounter', + ['counter' => 89] + ); + $this->setMethodStaticVariables( + NamespacedClassWithStaticVariables::class, + 'theCounter', + ['counter' => 89, 'step' => 12] + ); + $this->setMethodStaticVariables( + NamespacedClassWithStaticVariables::class, + 'theStaticCounter', + ['counter' => 14, 'step' => 13] + ); + } + + /** + * It should reset methods static variables between tests + * + * @test + */ + public function should_reset_methods_static_variables_between_tests(): void + { + $this->assertEquals( + ['counter' => 0, 'step' => 2], + $this->getMethodStaticVariables(SomeGlobalClassWithStaticVariables::class, 'theCounter') + ); + $this->assertEquals( + ['counter' => 0, 'step' => 2], + $this->getMethodStaticVariables(SomeGlobalClassWithStaticVariables::class, 'theStaticCounter') + ); + $this->assertEquals( + ['counter' => 0], + $this->getMethodStaticVariables(NamespacedClassWithStaticVariables::class, 'theCounter') + ); + $this->assertEquals( + ['counter' => 0], + $this->getMethodStaticVariables(NamespacedClassWithStaticVariables::class, 'theStaticCounter') + ); + } + + /** + * It should not throw if trying to reset a not set method static variable + * + * @test + */ + public function should_not_throw_if_trying_to_reset_a_not_set_method_static_variable(): void + { + $this->resetMethodStaticVariables(SomeGlobalClassWithStaticVariables::class, 'theCounter'); + $this->resetMethodStaticVariables(SomeGlobalClassWithStaticVariables::class, 'theStaticCounter'); + $this->resetMethodStaticVariables(NamespacedClassWithStaticVariables::class, 'theCounter'); + $this->resetMethodStaticVariables(NamespacedClassWithStaticVariables::class, 'theStaticCounter'); + } + + /** + * It should allow setting a function static variables + * + * @test + */ + public function should_allow_setting_a_function_static_variables(): void + { + $this->assertEquals( + ['counter' => 0, 'step' => 2], + $this->getFunctionStaticVariables('withStaticVariable') + ); + + $this->setFunctionStaticVariables( + 'withStaticVariable', + array_merge( + $this->getFunctionStaticVariables('withStaticVariable'), + ['counter' => 23] + ) + ); + + $this->assertEquals(23, withStaticVariable()); + + $this->resetFunctionStaticVariables('withStaticVariable'); + + $this->assertEquals(0, withStaticVariable()); + $this->assertEquals(['counter' => 2, 'step' => 2], $this->getFunctionStaticVariables('withStaticVariable')); + + $this->assertEquals( + ['counter' => 0, 'step' => 2], + $this->getFunctionStaticVariables('lucatume\WPBrowser\Acme\Project\withStaticVariable') + ); + + $this->setFunctionStaticVariables( + 'lucatume\WPBrowser\Acme\Project\withStaticVariable', + array_merge( + $this->getFunctionStaticVariables('lucatume\WPBrowser\Acme\Project\withStaticVariable'), + ['counter' => 23] + ) + ); + + $this->assertEquals(23, \lucatume\WPBrowser\Acme\Project\withStaticVariable()); + + $this->resetFunctionStaticVariables('lucatume\WPBrowser\Acme\Project\withStaticVariable'); + + $this->assertEquals(0, \lucatume\WPBrowser\Acme\Project\withStaticVariable()); + $this->assertEquals( + ['counter' => 2, 'step' => 2], + $this->getFunctionStaticVariables('lucatume\WPBrowser\Acme\Project\withStaticVariable') + ); + + $this->setFunctionStaticVariables( + 'withStaticVariable', + ['counter' => 89, 'step' => 3] + ); + + $this->setFunctionStaticVariables( + 'lucatume\WPBrowser\Acme\Project\withStaticVariable', + ['counter' => 89, 'step' => 3] + ); + } + + /** + * It should reset function static variables between tests + * + * @test + */ + public function should_reset_function_static_variables_between_tests(): void + { + $this->assertEquals(2, withStaticVariable()); + $this->assertEquals(2, \lucatume\WPBrowser\Acme\Project\withStaticVariable()); + } + + /** + * It should not throw if trying to reset not set function static variable + * + * @test + */ + public + function should_not_throw_if_trying_to_reset_not_set_function_static_variable(): void + { + $this->resetFunctionStaticVariables('withStaticVariable'); + $this->resetFunctionStaticVariables('lucatume\WPBrowser\Acme\Project\withStaticVariable'); + } + + /** + * It should allow adding and removing functions + * + * @test + */ + public function should_allow_adding_and_removing_functions(): void + { + $this->assertFalse(function_exists('addTwentyThree')); + + $this->addFunction('addTwentyThree', function (int $number) { + return $number + 23; + }); + + $this->assertTrue(function_exists('addTwentyThree')); + $this->assertEquals(89, addTwentyThree(66)); + + $this->removeFunction('addTwentyThree'); + + $this->assertFalse(function_exists('addTwentyThree')); + + $this->addFunction('addTwentyThree', function (int $number) { + return $number + 23; + }); + + $this->assertFalse(function_exists('Acme\Project\addTwentyThree')); + + $this->addFunction('Acme\Project\addTwentyThree', function (int $number) { + return $number + 23; + }); + + $this->assertTrue(function_exists('Acme\Project\addTwentyThree')); + $this->assertEquals(89, \Acme\Project\addTwentyThree(66)); + + $this->removeFunction('Acme\Project\addTwentyThree'); + + $this->assertFalse(function_exists('Acme\Project\addTwentyThree')); + + $this->addFunction('Acme\Project\addTwentyThree', function (int $number) { + return $number + 23; + }); + } + + /** + * It should remove added functions between tests + * + * @test + */ + public function should_remove_added_functions_between_tests(): void + { + $this->assertFalse(function_exists('addTwentyThree')); + $this->assertFalse(function_exists('Acme\Project\addTwentyThree')); + } + + /** + * It should not throw if trying to remove a not added function + * + * @test + */ + public function should_not_throw_if_trying_to_remove_a_not_added_function(): void + { + $this->removeFunction('addTwentyThree'); + $this->removeFunction('Acme\Project\addTwentyThree'); + } + + /** + * It should allow preventing exit + * + * @test + */ + public function should_allow_preventing_exit(): void + { + $this->preventExit(); + + $this->assertEquals(1,ini_get('uopz.exit')); + ob_start(); + echo "Print this and die\n"; + die(); + + $this->assertEquals("Print this and die\n",ob_get_clean()); + } + + /** + * It should not throw if trying to allow exit when exit not prevented + * + * @test + */ + public function should_not_throw_if_trying_to_allow_exit_when_exit_not_prevented(): void + { + $this->allowExit(); + } + + /** + * It should restore exit between tests + * + * @test + */ + public function should_restore_exit_between_tests(): void + { + $this->assertEquals(1,ini_get('uopz.exit')); + } +} diff --git a/tests/unit/lucatume/WPBrowser/Utils/ChromedriverInstallerTest.php b/tests/unit/lucatume/WPBrowser/Utils/ChromedriverInstallerTest.php index e3e687608..c8c4c2a78 100644 --- a/tests/unit/lucatume/WPBrowser/Utils/ChromedriverInstallerTest.php +++ b/tests/unit/lucatume/WPBrowser/Utils/ChromedriverInstallerTest.php @@ -6,7 +6,7 @@ use lucatume\WPBrowser\Exceptions\InvalidArgumentException; use lucatume\WPBrowser\Exceptions\RuntimeException; use lucatume\WPBrowser\Tests\Traits\TmpFilesCleanup; -use lucatume\WPBrowser\Tests\Traits\UopzFunctions; +use lucatume\WPBrowser\Traits\UopzFunctions; class ChromedriverInstallerTest extends \Codeception\Test\Unit { @@ -20,7 +20,7 @@ class ChromedriverInstallerTest extends \Codeception\Test\Unit */ public function should_throw_if_detected_platform_is_not_supported(): void { - $this->uopzSetFunctionReturn('php_uname', 'Lorem'); + $this->setFunctionReturn('php_uname', 'Lorem'); $this->expectException(RuntimeException::class); $this->expectExceptionCode(ChromedriverInstaller::ERR_DETECT_PLATFORM); @@ -48,7 +48,7 @@ public function should_throw_if_specified_platform_is_not_supported(): void */ public function should_throw_if_specified_binary_cannot_be_found(): void { - $this->uopzSetFunctionReturn('is_file', fn(string $file) => !str_contains($file, 'chrome'), true); + $this->setFunctionReturn('is_file', fn(string $file) => !str_contains($file, 'chrome'), true); $this->expectException(RuntimeException::class); $this->expectExceptionCode(ChromedriverInstaller::ERR_INVALID_BINARY); @@ -80,8 +80,8 @@ public function should_throw_if_binary_cannot_be_found_in_default_paths_for_plat string $binNamePattern ): void { $isNotAnExecutableFile = fn(string $file) => !str_contains($file, $binNamePattern); - $this->uopzSetFunctionReturn('is_file', $isNotAnExecutableFile, true); - $this->uopzSetFunctionReturn('is_executable', $isNotAnExecutableFile, true); + $this->setFunctionReturn('is_file', $isNotAnExecutableFile, true); + $this->setFunctionReturn('is_executable', $isNotAnExecutableFile, true); $this->expectException(RuntimeException::class); $this->expectExceptionCode(ChromedriverInstaller::ERR_INVALID_BINARY); @@ -95,7 +95,7 @@ public function should_throw_if_binary_cannot_be_found_in_default_paths_for_plat */ public function should_throw_if_binary_cannot_be_executed(): void { - $this->uopzSetFunctionReturn('is_executable', function (string $file): bool { + $this->setFunctionReturn('is_executable', function (string $file): bool { return !str_contains($file, 'chrome') && is_executable($file); }, true); @@ -112,7 +112,7 @@ public function should_throw_if_binary_cannot_be_executed(): void */ public function should_throw_if_specified_binary_is_not_valid(): void { - $this->uopzSetFunctionReturn('is_executable', function (string $file): bool { + $this->setFunctionReturn('is_executable', function (string $file): bool { return !str_contains($file, 'Chromium') && is_executable($file); }, true); @@ -150,7 +150,7 @@ public function should_throw_if_version_from_binary_is_not_a_string(): void */ public function should_throw_if_version_from_binary_has_not_correct_format(): void { - $this->uopzSetFunctionReturn('exec', 'Could not start Google Chrome.'); + $this->setFunctionReturn('exec', 'Could not start Google Chrome.'); $this->expectException(RuntimeException::class); $this->expectExceptionCode(ChromedriverInstaller::ERR_INVALID_VERSION_FORMAT); @@ -206,7 +206,7 @@ public function should_throw_if_trying_to_install_to_non_existing_directory(): v */ public function should_throw_if_it_cannot_get_milestone_downloads(): void { - $this->uopzSetFunctionReturn('file_get_contents', function (string $file): string|false { + $this->setFunctionReturn('file_get_contents', function (string $file): string|false { return str_contains($file, 'chrome-for-testing') ? false : file_get_contents($file); }, true); @@ -226,7 +226,7 @@ public function should_throw_if_it_cannot_get_milestone_downloads(): void */ public function should_throw_if_response_is_not_valid_json(): void { - $this->uopzSetFunctionReturn('file_get_contents', function (string $file): string|false { + $this->setFunctionReturn('file_get_contents', function (string $file): string|false { return str_contains($file, 'chrome-for-testing') ? '{}' : file_get_contents($file); }, true); @@ -249,7 +249,7 @@ public function should_throw_if_response_is_not_valid_json(): void */ public function should_throw_if_download_url_for_chrome_version_cannot_be_found_in_milestone_downloads(): void { - $this->uopzSetFunctionReturn('file_get_contents', function (string $file): string|false { + $this->setFunctionReturn('file_get_contents', function (string $file): string|false { return str_contains($file, 'chrome-for-testing') ? '{"milestones":{"116": {"downloads":{"chrome":{},"chromedriver":{}}}}}' : file_get_contents($file); @@ -271,8 +271,8 @@ public function should_throw_if_download_url_for_chrome_version_cannot_be_found_ */ public function should_throw_if_existing_zip_file_cannot_be_removed(): void { - $this->uopzSetFunctionReturn('sys_get_temp_dir', codecept_output_dir()); - $this->uopzSetFunctionReturn('unlink', function (string $file): bool { + $this->setFunctionReturn('sys_get_temp_dir', codecept_output_dir()); + $this->setFunctionReturn('unlink', function (string $file): bool { return preg_match('~chromedriver\\.zip$~', $file) ? false : unlink($file); }, true); @@ -293,8 +293,8 @@ public function should_throw_if_existing_zip_file_cannot_be_removed(): void public function should_throw_if_existing_binary_cannot_be_removed(): void { $dir = Filesystem::tmpDir('chromedriver_installer_', ['chromedriver' => '']); - $this->uopzSetFunctionReturn('sys_get_temp_dir', codecept_output_dir()); - $this->uopzSetFunctionReturn('unlink', function (string $file) use ($dir): bool { + $this->setFunctionReturn('sys_get_temp_dir', codecept_output_dir()); + $this->setFunctionReturn('unlink', function (string $file) use ($dir): bool { return $file === $dir . '/chromedriver' ? false : unlink($file); }, true); @@ -314,8 +314,8 @@ public function should_throw_if_existing_binary_cannot_be_removed(): void public function should_throw_if_new_binary_cannot_be_made_executable(): void { $dir = Filesystem::tmpDir('chromedriver_installer_'); - $this->uopzSetFunctionReturn('sys_get_temp_dir', codecept_output_dir()); - $this->uopzSetFunctionReturn('chmod', false); + $this->setFunctionReturn('sys_get_temp_dir', codecept_output_dir()); + $this->setFunctionReturn('chmod', false); $ci = new ChromedriverInstaller(null, 'linux64', codecept_data_dir('bins/chrome-mock')); @@ -334,7 +334,7 @@ public function should_correctly_install_chromedriver(): void { $tmpDir = Filesystem::tmpDir('chromedriver_installer_tmp_'); $dir = Filesystem::tmpDir('chromedriver_installer_'); - $this->uopzSetFunctionReturn('sys_get_temp_dir', $tmpDir); + $this->setFunctionReturn('sys_get_temp_dir', $tmpDir); $ci = new ChromedriverInstaller(null, 'linux64', codecept_data_dir('bins/chrome-mock')); diff --git a/tests/unit/lucatume/WPBrowser/Utils/ComposerTest.php b/tests/unit/lucatume/WPBrowser/Utils/ComposerTest.php index c4a13d62b..f59cf6667 100644 --- a/tests/unit/lucatume/WPBrowser/Utils/ComposerTest.php +++ b/tests/unit/lucatume/WPBrowser/Utils/ComposerTest.php @@ -6,9 +6,9 @@ use lucatume\WPBrowser\Adapters\Symfony\Component\Process\Process; use lucatume\WPBrowser\Exceptions\RuntimeException; use lucatume\WPBrowser\Tests\Traits\ClassStubs; -use lucatume\WPBrowser\Tests\Traits\UopzFunctions; -use PHPUnit\Framework\Assert; +use lucatume\WPBrowser\Traits\UopzFunctions; use lucatume\WPBrowser\Utils\Filesystem as FS; +use PHPUnit\Framework\Assert; class ComposerTest extends \Codeception\Test\Unit { @@ -34,7 +34,7 @@ public function should_throw_if_trying_to_build_on_non_existing_file(): void */ public function should_throw_if_trying_to_build_on_non_readable_file(): void { - $this->uopzSetFunctionReturn('is_readable', false); + $this->setFunctionReturn('is_readable', false); $this->expectException(RuntimeException::class); $this->expectExceptionCode(Composer::ERR_FILE_NOT_FOUND); new Composer(__FILE__); @@ -78,7 +78,7 @@ public function should_throw_if_json_file_contents_cannot_be_decoded(): void */ public function should_throw_if_file_contents_cannot_be_read(): void { - $this->uopzSetFunctionReturn('file_get_contents', false); + $this->setFunctionReturn('file_get_contents', false); $this->expectException(RuntimeException::class); $this->expectExceptionCode(Composer::ERR_FILE_UNREADABLE); new Composer(__FILE__); @@ -117,7 +117,7 @@ public function should_throw_if_write_fails(): void $composer->requireDev(['foo/bar' => '1.0.0', 'foo/baz' => '^2.3']); $hash = md5(microtime()); $outputFile = sys_get_temp_dir() . "/$hash-composer.json"; - $this->uopzSetFunctionReturn('file_put_contents', false); + $this->setFunctionReturn('file_put_contents', false); $this->expectException(RuntimeException::class); $this->expectExceptionCode(Composer::ERR_FILE_WRITE_FAILED); $composer->write(); @@ -131,7 +131,7 @@ public function should_throw_if_write_fails(): void public function should_allow_updating_the_composer_file(): void { $built = 0; - $this->uopzSetMock(Process::class, + $this->setClassMock(Process::class, $this->makeEmptyClass(Process::class, [ '__construct' => function () use (&$built) { Assert::assertEquals(['composer', 'update', '--no-interaction'], func_get_args()[0]); @@ -157,7 +157,7 @@ public function should_allow_updating_the_composer_file(): void public function should_allow_updating_the_composer_file_for_a_specific_package(): void { $built = 0; - $this->uopzSetMock(Process::class, + $this->setClassMock(Process::class, $this->makeEmptyClass(Process::class, [ '__construct' => function () use (&$built) { Assert::assertEquals(['composer', 'update', '--no-interaction', 'foo/baz'], func_get_args()[0]); @@ -183,7 +183,7 @@ public function should_allow_updating_the_composer_file_for_a_specific_package() */ public function should_throw_if_update_fails(): void { - $this->uopzSetMock(Process::class, + $this->setClassMock(Process::class, $this->makeEmptyClass(Process::class, [ '__construct' => function () { Assert::assertEquals(['composer', 'update', '--no-interaction'], func_get_args()[0]); diff --git a/tests/unit/lucatume/WPBrowser/WordPress/Database/MysqlDatabaseTest.php b/tests/unit/lucatume/WPBrowser/WordPress/Database/MysqlDatabaseTest.php index e20389512..bfd9f88fc 100644 --- a/tests/unit/lucatume/WPBrowser/WordPress/Database/MysqlDatabaseTest.php +++ b/tests/unit/lucatume/WPBrowser/WordPress/Database/MysqlDatabaseTest.php @@ -5,7 +5,7 @@ use Codeception\Test\Unit; use lucatume\WPBrowser\Tests\Traits\TmpFilesCleanup; -use lucatume\WPBrowser\Tests\Traits\UopzFunctions; +use lucatume\WPBrowser\Traits\UopzFunctions; use lucatume\WPBrowser\Utils\Env; use lucatume\WPBrowser\Utils\Filesystem as FS; use lucatume\WPBrowser\Utils\Random; @@ -17,7 +17,6 @@ use lucatume\WPBrowser\WordPress\InstallationState\Single; use lucatume\WPBrowser\WordPress\WPConfigFile; use PDO; -use tad\Codeception\SnapshotAssertions\SnapshotAssertions; class MysqlDatabaseTest extends Unit { @@ -175,7 +174,7 @@ public function should_throw_if_dump_cannot_be_opened_to_import(): void $this->expectException(DbException::class); $this->expectExceptionCode(DbException::DUMP_FILE_NOT_READABLE); - $this->uopzSetFunctionReturn('fopen', false); + $this->setFunctionReturn('fopen', false); $db->import(codecept_data_dir('files/test-dump-001.sql')); } @@ -279,7 +278,7 @@ public function should_throw_if_dump_file_cannot_be_exported(): void $dbUser = Env::get('WORDPRESS_DB_USER'); $dbPassword = Env::get('WORDPRESS_DB_PASSWORD'); - $this->uopzSetFunctionReturn('fwrite', false); + $this->setFunctionReturn('fwrite', false); $this->expectException(DbException::class); $this->expectExceptionCode(DbException::FAILED_DUMP); diff --git a/tests/unit/lucatume/WPBrowser/WordPress/Database/SqliteDatabaseTest.php b/tests/unit/lucatume/WPBrowser/WordPress/Database/SqliteDatabaseTest.php index 86419fcdc..3d8b36102 100644 --- a/tests/unit/lucatume/WPBrowser/WordPress/Database/SqliteDatabaseTest.php +++ b/tests/unit/lucatume/WPBrowser/WordPress/Database/SqliteDatabaseTest.php @@ -3,9 +3,9 @@ namespace lucatume\WPBrowser\WordPress\Database; use lucatume\WPBrowser\Tests\Traits\TmpFilesCleanup; -use lucatume\WPBrowser\Tests\Traits\UopzFunctions; -use lucatume\WPBrowser\WordPress\DbException; +use lucatume\WPBrowser\Traits\UopzFunctions; use lucatume\WPBrowser\Utils\Filesystem as FS; +use lucatume\WPBrowser\WordPress\DbException; use lucatume\WPBrowser\WordPress\Installation; use lucatume\WPBrowser\WordPress\WPConfigFile; @@ -33,7 +33,7 @@ public function should_throw_if_building_on_non_existing_directory(): void */ public function should_throw_if_building_on_non_writable_directory(): void { - $this->uopzSetFunctionReturn('is_writable', false); + $this->setFunctionReturn('is_writable', false); $this->expectException(DbException::class); $this->expectExceptionCode(SQLiteDatabase::ERR_DIR_NOT_FOUND); new SQLiteDatabase(__DIR__); @@ -140,7 +140,7 @@ public function should_throw_if_file_cannot_be_unlinked_during_drop(): void $file = '/db.sqlite'; $db = new SQLiteDatabase($dir, $file); $db->create(); - $this->uopzSetFunctionReturn('unlink', false); + $this->setFunctionReturn('unlink', false); $this->expectException(DbException::class); $this->expectExceptionCode(SQLiteDatabase::ERR_DROP_DB_FAILED); $db->drop(); @@ -247,7 +247,7 @@ public function should_throw_if_trying_to_import_import_non_readable_file(): voi $dump = codecept_data_dir('dump.sqlite'); $dir = FS::tmpDir('sqlite_'); $file = 'db.sqlite'; - $this->uopzSetFunctionReturn('file_get_contents', false); + $this->setFunctionReturn('file_get_contents', false); $this->expectException(DbException::class); $this->expectExceptionCode(DbException::DUMP_FILE_NOT_READABLE); @@ -334,7 +334,7 @@ public function should_throw_if_database_dump_file_cannot_be_written(): void $db = new SQLiteDatabase($dir, $file); $db->import($dump); - $this->uopzSetFunctionReturn('file_put_contents', false); + $this->setFunctionReturn('file_put_contents', false); $this->expectException(DbException::class); $this->expectExceptionCode(DbException::FAILED_DUMP); diff --git a/tests/unit/lucatume/WPBrowser/WordPress/InstallationState/ConfiguredTest.php b/tests/unit/lucatume/WPBrowser/WordPress/InstallationState/ConfiguredTest.php index 0399bf959..0b9ab3f71 100644 --- a/tests/unit/lucatume/WPBrowser/WordPress/InstallationState/ConfiguredTest.php +++ b/tests/unit/lucatume/WPBrowser/WordPress/InstallationState/ConfiguredTest.php @@ -7,7 +7,7 @@ use Exception; use lucatume\WPBrowser\Tests\Traits\ClassStubs; use lucatume\WPBrowser\Tests\Traits\TmpFilesCleanup; -use lucatume\WPBrowser\Tests\Traits\UopzFunctions; +use lucatume\WPBrowser\Traits\UopzFunctions; use lucatume\WPBrowser\Utils\Env; use lucatume\WPBrowser\Utils\Filesystem as FS; use lucatume\WPBrowser\Utils\Random; diff --git a/tests/unit/lucatume/WPBrowser/WordPress/InstallationState/EmptyDirTest.php b/tests/unit/lucatume/WPBrowser/WordPress/InstallationState/EmptyDirTest.php index 6628cc74d..25cabafdc 100644 --- a/tests/unit/lucatume/WPBrowser/WordPress/InstallationState/EmptyDirTest.php +++ b/tests/unit/lucatume/WPBrowser/WordPress/InstallationState/EmptyDirTest.php @@ -5,7 +5,7 @@ use Codeception\Test\Unit; use lucatume\WPBrowser\Tests\Traits\TmpFilesCleanup; -use lucatume\WPBrowser\Tests\Traits\UopzFunctions; +use lucatume\WPBrowser\Traits\UopzFunctions; use lucatume\WPBrowser\Utils\Env; use lucatume\WPBrowser\Utils\Filesystem as FS; use lucatume\WPBrowser\Utils\Random; diff --git a/tests/unit/lucatume/WPBrowser/WordPress/InstallationState/MultisiteTest.php b/tests/unit/lucatume/WPBrowser/WordPress/InstallationState/MultisiteTest.php index c70282515..d713b9622 100644 --- a/tests/unit/lucatume/WPBrowser/WordPress/InstallationState/MultisiteTest.php +++ b/tests/unit/lucatume/WPBrowser/WordPress/InstallationState/MultisiteTest.php @@ -6,7 +6,7 @@ use Codeception\Test\Unit; use lucatume\WPBrowser\Exceptions\InvalidArgumentException; use lucatume\WPBrowser\Tests\Traits\TmpFilesCleanup; -use lucatume\WPBrowser\Tests\Traits\UopzFunctions; +use lucatume\WPBrowser\Traits\UopzFunctions; use lucatume\WPBrowser\Utils\Env; use lucatume\WPBrowser\Utils\Filesystem as FS; use lucatume\WPBrowser\Utils\Random; diff --git a/tests/unit/lucatume/WPBrowser/WordPress/InstallationState/ScaffoldedTest.php b/tests/unit/lucatume/WPBrowser/WordPress/InstallationState/ScaffoldedTest.php index 9a15d9d4c..2f63fe2ba 100644 --- a/tests/unit/lucatume/WPBrowser/WordPress/InstallationState/ScaffoldedTest.php +++ b/tests/unit/lucatume/WPBrowser/WordPress/InstallationState/ScaffoldedTest.php @@ -5,7 +5,7 @@ use Codeception\Test\Unit; use lucatume\WPBrowser\Tests\Traits\TmpFilesCleanup; -use lucatume\WPBrowser\Tests\Traits\UopzFunctions; +use lucatume\WPBrowser\Traits\UopzFunctions; use lucatume\WPBrowser\Utils\Env; use lucatume\WPBrowser\Utils\Filesystem as FS; use lucatume\WPBrowser\Utils\Random; @@ -656,7 +656,7 @@ public function should_throw_if_sqlite_plugin_db_copy_file_cannot_be_read(): voi $scaffolded = new Scaffolded($wpRootDir); - $this->uopzSetFunctionReturn('file_get_contents', function (string $file) { + $this->setFunctionReturn('file_get_contents', function (string $file) { if (str_ends_with($file, 'db.copy')) { return false; } @@ -681,7 +681,7 @@ public function should_throw_if_sqlite_drop_in_placement_fails(): void $scaffolded = new Scaffolded($wpRootDir); - $this->uopzSetFunctionReturn('file_put_contents', function (string $file) { + $this->setFunctionReturn('file_put_contents', function (string $file) { if (str_ends_with($file, 'db.php')) { return false; } diff --git a/tests/unit/lucatume/WPBrowser/WordPress/InstallationState/SingleTest.php b/tests/unit/lucatume/WPBrowser/WordPress/InstallationState/SingleTest.php index bd329c725..0862f4c97 100644 --- a/tests/unit/lucatume/WPBrowser/WordPress/InstallationState/SingleTest.php +++ b/tests/unit/lucatume/WPBrowser/WordPress/InstallationState/SingleTest.php @@ -6,7 +6,7 @@ use Codeception\Test\Unit; use lucatume\WPBrowser\Exceptions\InvalidArgumentException; use lucatume\WPBrowser\Tests\Traits\TmpFilesCleanup; -use lucatume\WPBrowser\Tests\Traits\UopzFunctions; +use lucatume\WPBrowser\Traits\UopzFunctions; use lucatume\WPBrowser\Utils\Env; use lucatume\WPBrowser\Utils\Filesystem as FS; use lucatume\WPBrowser\Utils\Random; @@ -238,7 +238,7 @@ public function should_throw_if_wp_config_php_file_contents_cannot_be_read_durin $single = new Single($wpRootDir, $wpRootDir . '/wp-config.php'); - $this->uopzSetFunctionReturn('file_get_contents', false); + $this->setFunctionReturn('file_get_contents', false); $this->expectException(InstallationException::class); $this->expectExceptionCode(InstallationException::WP_CONFIG_FILE_NOT_FOUND); @@ -272,7 +272,7 @@ public function should_throw_if_the_placeholder_is_not_found_in_the_wp_config_ph $single = new Single($wpRootDir, $wpConfigFilePath); - $this->uopzSetFunctionReturn('file_get_contents', function (string $file) use ($wpConfigFilePath) { + $this->setFunctionReturn('file_get_contents', function (string $file) use ($wpConfigFilePath) { if ($file === $wpConfigFilePath) { return 'uopzSetFunctionReturn('file_put_contents', false); + $this->setFunctionReturn('file_put_contents', false); $this->expectException(InstallationException::class); $this->expectExceptionCode(InstallationException::WRITE_ERROR); diff --git a/tests/unit/lucatume/WPBrowser/WordPress/InstallationTest.php b/tests/unit/lucatume/WPBrowser/WordPress/InstallationTest.php index 3a3a27998..5f3235ede 100644 --- a/tests/unit/lucatume/WPBrowser/WordPress/InstallationTest.php +++ b/tests/unit/lucatume/WPBrowser/WordPress/InstallationTest.php @@ -4,10 +4,9 @@ namespace lucatume\WPBrowser\WordPress; use Codeception\Test\Unit; -use lucatume\WPBrowser\Tests\FSTemplates\BedrockProject; use lucatume\WPBrowser\Tests\Traits\MainInstallationAccess; use lucatume\WPBrowser\Tests\Traits\TmpFilesCleanup; -use lucatume\WPBrowser\Tests\Traits\UopzFunctions; +use lucatume\WPBrowser\Traits\UopzFunctions; use lucatume\WPBrowser\Utils\Env; use lucatume\WPBrowser\Utils\Filesystem as FS; use lucatume\WPBrowser\Utils\Random; @@ -46,7 +45,7 @@ public function should_throw_when_building_on_non_existing_root_directory(): voi public function should_throw_when_building_on_non_writable_root_directory(): void { $tmpDir = FS::tmpDir('installation_'); - $this->uopzSetFunctionReturn('is_writable', + $this->setFunctionReturn('is_writable', fn(string $dir) => !($dir === $tmpDir . '/') && is_writable($dir), true ); @@ -65,7 +64,7 @@ public function should_throw_when_building_on_non_writable_root_directory(): voi public function should_throw_when_building_on_non_readable_root_directory() { $tmpDir = FS::tmpDir('installation_'); - $this->uopzSetFunctionReturn('is_readable', + $this->setFunctionReturn('is_readable', fn(string $dir) => !($dir === $tmpDir . '/') && is_readable($dir), true ); diff --git a/tests/unit/lucatume/WPBrowser/WordPress/WpConfigFileGeneratorTest.php b/tests/unit/lucatume/WPBrowser/WordPress/WpConfigFileGeneratorTest.php index 227477279..77f1b42d6 100644 --- a/tests/unit/lucatume/WPBrowser/WordPress/WpConfigFileGeneratorTest.php +++ b/tests/unit/lucatume/WPBrowser/WordPress/WpConfigFileGeneratorTest.php @@ -4,7 +4,7 @@ use Codeception\Test\Unit; use lucatume\WPBrowser\Tests\Traits\TmpFilesCleanup; -use lucatume\WPBrowser\Tests\Traits\UopzFunctions; +use lucatume\WPBrowser\Traits\UopzFunctions; use lucatume\WPBrowser\Utils\Env; use lucatume\WPBrowser\Utils\Filesystem as FS; use lucatume\WPBrowser\WordPress\Database\MysqlDatabase; @@ -58,7 +58,7 @@ public function should_throw_if_wp_config_sample_php_file_cannot_be_read(): void 'wp-settings.php' => ' 'uopzSetFunctionReturn('file_get_contents', static function ($file) use ($wpRootDir) { + $this->setFunctionReturn('file_get_contents', static function ($file) use ($wpRootDir) { return $file === $wpRootDir . '/wp-config-sample.php' ? false : file_get_contents($file); }, true); diff --git a/tests/wploadersuite/AjaxTest.php b/tests/wploadersuite/AjaxTest.php index c5399bdb4..c8fd7a410 100644 --- a/tests/wploadersuite/AjaxTest.php +++ b/tests/wploadersuite/AjaxTest.php @@ -1,7 +1,7 @@