diff --git a/docs/usage/mocking-wp-action-and-filter-hooks.md b/docs/usage/mocking-wp-action-and-filter-hooks.md index fde8a9b..973ec40 100644 --- a/docs/usage/mocking-wp-action-and-filter-hooks.md +++ b/docs/usage/mocking-wp-action-and-filter-hooks.md @@ -140,4 +140,25 @@ final class MyClassTest extends TestCase $this->assertEquals('Default', (new MyClass())->filterContent()); } } -``` \ No newline at end of file +``` + +## Asserting that actions and filters have been removed + +Similarly, we can test that actions and filters are removed when expected, e.g.removing another plugin's admin notice. This is done using `WP_Mock::expectActionRemoved()` and `WP_Mock::expectFilterRemoved()`. Or conversely, we can confirm that they have _not_ been removed using `WP_Mock::expectActionNotRemoved()` and `WP_Mock::expectFilterNotRemoved()`. The latter functions are useful where a function being tested returns early in some scenarios, and we want to ensure that the hooks are not removed in that case. + +```php +use MyPlugin\MyClass; +use WP_Mock\Tools\TestCase as TestCase; + +final class MyClassTest extends TestCase +{ + public function testRemoveAction() : void + { + $classInstance = new MyClass(); + + WP_Mock::expectActionRemoved('admin_notices', 'invasive_admin_notice'); + + $classInstance->removeInvasiveAdminNotice(); + } +} +``` diff --git a/php/WP_Mock.php b/php/WP_Mock.php index 49ef1f3..e73a5f9 100644 --- a/php/WP_Mock.php +++ b/php/WP_Mock.php @@ -546,4 +546,88 @@ public static function getDeprecatedMethodListener(): DeprecatedMethodListener { return static::$deprecatedMethodListener; } + + /** + * Adds an expectation that an action should be removed. + * + * @param string $action the action hook name + * @param string|callable-string|callable|Type $callback the callable to be removed + * @param int|Type|null $priority the priority it should be registered at + * + * @return void + * @throws InvalidArgumentException + */ + public static function expectActionRemoved(string $action, $callback, $priority = null) : void + { + self::userFunction( + 'remove_action', + array( + 'args' => array_filter(func_get_args()), + 'times' => 1, + ) + ); + } + + /** + * Adds an expectation that an action should not be removed. + * + * @param string $action the action hook name + * @param null|string|callable-string|callable|Type $callback the callable to be removed + * @param int|Type|null $priority optional priority for the registered callback that is being removed + * + * @return void + * @throws InvalidArgumentException + */ + public static function expectActionNotRemoved(string $action, $callback, $priority = null) : void + { + self::userFunction( + 'remove_action', + array( + 'args' => array_filter(func_get_args()), + 'times' => 0, + ) + ); + } + + /** + * Adds an expectation that a filter should be removed. + * + * @param string $filter the filter name + * @param string|callable-string|callable|Type $callback the callable to be removed + * @param int|Type|null $priority the registered priority + * + * @return void + * @throws InvalidArgumentException + */ + public static function expectFilterRemoved(string $filter, $callback, $priority = null) : void + { + self::userFunction( + 'remove_filter', + array( + 'args' => array_filter(func_get_args()), + 'times' => 1, + ) + ); + } + + /** + * Adds an expectation that a filter should not be removed. + * + * @param string $filter the filter name + * @param null|string|callable-string|callable|Type $callback the callable to be removed + * @param int|Type|null $priority optional priority for the registered callback that is being removed + * + * @return void + * @throws InvalidArgumentException + */ + public static function expectFilterNotRemoved(string $filter, $callback, $priority = null) : void + { + self::userFunction( + 'remove_filter', + array( + 'args' => array_filter(func_get_args()), + 'times' => 0, + ) + ); + } } diff --git a/tests/Integration/WP_MockTest.php b/tests/Integration/WP_MockTest.php index 3af7bb3..473d1a0 100644 --- a/tests/Integration/WP_MockTest.php +++ b/tests/Integration/WP_MockTest.php @@ -335,4 +335,60 @@ public function testCanExpectHooksNotAdded() : void $this->assertConditionsMet(); } + + /** + * @covers \WP_Mock::expectActionRemoved() + * @covers \WP_Mock::expectFilterRemoved() + * + * @return void + * @throws Exception + */ + public function testCanExpectHooksRemoved() : void + { + WP_Mock::expectActionRemoved('wpMockTestActionWithCallbackAndPriority', 'wpMockTestFunction', 10); + WP_Mock::expectFilterRemoved('wpMockTestFilterWithCallbackAndPriority', 'wpMockTestFunction', 10); + + WP_Mock::expectActionRemoved( + 'wpMockTestActionWithCallbackAndAnyPriority', + 'wpMockTestFunction', + \WP_Mock\Functions::type('int') + ); + WP_Mock::expectFilterRemoved( + 'wpMockTestFilterWithCallbackAndAnyPriority', + 'wpMockTestFunction', + \WP_Mock\Functions::type('int') + ); + + WP_Mock::expectActionRemoved('wpMockTestActionWithCallbackAndDefaultPriority', 'wpMockTestFunction'); + WP_Mock::expectFilterRemoved('wpMockTestFilterWithCallbackAndDefaultPriority', 'wpMockTestFunction'); + + remove_action('wpMockTestActionWithCallbackAndPriority', 'wpMockTestFunction', 10); + remove_filter('wpMockTestFilterWithCallbackAndPriority', 'wpMockTestFunction', 10); + + remove_action('wpMockTestActionWithCallbackAndAnyPriority', 'wpMockTestFunction', rand(1,100)); + remove_filter('wpMockTestFilterWithCallbackAndAnyPriority', 'wpMockTestFunction', rand(1,100)); + + remove_action('wpMockTestActionWithCallbackAndDefaultPriority', 'wpMockTestFunction'); + remove_filter('wpMockTestFilterWithCallbackAndDefaultPriority', 'wpMockTestFunction'); + + $this->assertConditionsMet(); + } + + /** + * @covers \WP_Mock::expectActionNotRemoved() + * @covers \WP_Mock::expectFilterNotRemoved() + * + * @return void + * @throws Exception + */ + public function testCanExpectHooksNotRemoved() : void + { + WP_Mock::expectActionNotRemoved('wpMockTestActionWithCallbackAndPriority', 'wpMockTestFunction', 10); + WP_Mock::expectFilterNotRemoved('wpMockTestFilterWithCallbackAndPriority', 'wpMockTestFunction', 10); + + WP_Mock::expectActionNotRemoved('wpMockTestActionWithCallbackAndDefaultPriority', 'wpMockTestFunction'); + WP_Mock::expectFilterNotRemoved('wpMockTestFilterWithCallbackAndDefaultPriority', 'wpMockTestFunction'); + + $this->assertConditionsMet(); + } }