• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

10up / wp_mock / 21058610162

16 Jan 2026 07:06AM UTC coverage: 67.215% (+1.1%) from 66.142%
21058610162

Pull #246

github

web-flow
Merge d6e40f7d9 into 446ea7083
Pull Request #246: Add convenience `::expectFilterRemoved()` `::expectActionRemoved()` Fix #157

28 of 28 new or added lines in 1 file covered. (100.0%)

1 existing line in 1 file now uncovered.

531 of 790 relevant lines covered (67.22%)

2.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.59
/php/WP_Mock.php
1
<?php
2

3
/**
4
 * @package WP_Mock
5
 * @copyright 2013-2024 by the contributors
6
 * @license BSD-3-Clause
7
 * @see ../LICENSE.md
8
 */
9

10
use Mockery\Exception as MockeryException;
11
use Mockery\ExpectationInterface;
12
use WP_Mock\DeprecatedMethodListener;
13
use WP_Mock\Functions\Handler;
14
use WP_Mock\Matcher\FuzzyObject;
15
use Mockery\Matcher\Type;
16

17
/**
18
 * WP_Mock main class.
19
 */
20
class WP_Mock
21
{
22
    /**
23
     * @var \WP_Mock\EventManager
24
     */
25
    protected static $event_manager;
26

27
    /** @var WP_Mock\Functions */
28
    protected static $functionsManager;
29

30
    protected static $__bootstrapped = false;
31

32
    protected static $__use_patchwork = false;
33

34
    protected static $__strict_mode = false;
35

36
    /** @var DeprecatedMethodListener */
37
    protected static $deprecatedMethodListener;
38

39
    /**
40
     * @param boolean $use_patchwork
41
     */
42
    public static function setUsePatchwork($use_patchwork)
43
    {
44
        if (! self::$__bootstrapped) {
×
45
            self::$__use_patchwork = (bool) $use_patchwork;
×
46
        }
47
    }
48

49
    public static function usingPatchwork()
50
    {
51
        return (bool) self::$__use_patchwork;
16✔
52
    }
53

54
    /**
55
     * Check whether strict mode is turned on
56
     *
57
     * @return bool
58
     */
59
    public static function strictMode()
60
    {
61
        return (bool) self::$__strict_mode;
23✔
62
    }
63

64
    /**
65
     * Turns on strict mode
66
     */
67
    public static function activateStrictMode()
68
    {
69
        if (! self::$__bootstrapped) {
3✔
70
            self::$__strict_mode = true;
2✔
71
        }
72
    }
73

74
    /**
75
     * Bootstraps WP_Mock.
76
     *
77
     * @return void
78
     */
79
    public static function bootstrap(): void
80
    {
81
        if (! self::$__bootstrapped) {
18✔
82
            self::$__bootstrapped = true;
16✔
83

84
            static::$deprecatedMethodListener = new DeprecatedMethodListener();
16✔
85

86
            require_once __DIR__ . '/WP_Mock/API/function-mocks.php';
16✔
87
            require_once __DIR__ . '/WP_Mock/API/constant-mocks.php';
16✔
88

89
            if (self::usingPatchwork()) {
16✔
90
                $patchwork_path  = 'antecedent/patchwork/Patchwork.php';
×
91
                $possible_locations = [
×
92
                    'vendor',
×
93
                    '../..',
×
94
                ];
×
95

96
                foreach ($possible_locations as $loc) {
×
97
                    $path = __DIR__ . "/../$loc/$patchwork_path";
×
98

99
                    if (file_exists($path)) {
×
100
                        break;
×
101
                    }
102
                }
103

104
                // Will cause a fatal error if patchwork can't be found
105
                require_once($path);
×
106
            }
107

108
            self::setUp();
16✔
109
        }
110
    }
111

112
    /**
113
     * Make sure Mockery doesn't have anything set up already.
114
     */
115
    public static function setUp(): void
116
    {
117
        if (self::$__bootstrapped) {
45✔
118
            \Mockery::close();
45✔
119

120
            self::$event_manager    = new \WP_Mock\EventManager();
45✔
121
            self::$functionsManager = new \WP_Mock\Functions();
45✔
122
        } else {
UNCOV
123
            self::bootstrap();
×
124
        }
125
    }
126

127
    /**
128
     * Tear down anything built up inside Mockery when we're ready to do so.
129
     */
130
    public static function tearDown(): void
131
    {
132
        self::$event_manager->flush();
×
133
        self::$functionsManager->flush();
×
134

135
        Mockery::close();
×
136
        Handler::cleanup();
×
137
    }
138

139
    /**
140
     * Fire a specific (mocked) callback when an apply_filters() call is used.
141
     *
142
     * @param string $filter
143
     *
144
     * @return \WP_Mock\Filter
145
     */
146
    public static function onFilter($filter)
147
    {
148
        self::$event_manager->called($filter, 'filter');
6✔
149
        return self::$event_manager->filter($filter);
6✔
150
    }
151

152
    /**
153
     * Fire a specific (mocked) callback when a do_action() call is used.
154
     *
155
     * @param string $action
156
     *
157
     * @return \WP_Mock\Action
158
     */
159
    public static function onAction($action)
160
    {
161
        return self::$event_manager->action($action);
2✔
162
    }
163

164
    /**
165
     * Get a filter or action added callback object
166
     *
167
     * @param string $hook
168
     * @param string $type
169
     *
170
     * @return \WP_Mock\HookedCallback
171
     */
172
    public static function onHookAdded($hook, $type = 'filter')
173
    {
174
        return self::$event_manager->callback($hook, $type);
4✔
175
    }
176

177
    /**
178
     * Get a filter added callback object
179
     *
180
     * @param string $hook
181
     *
182
     * @return \WP_Mock\HookedCallback
183
     */
184
    public static function onFilterAdded($hook)
185
    {
186
        return self::onHookAdded($hook, 'filter');
3✔
187
    }
188

189
    /**
190
     * Get an action added callback object
191
     *
192
     * @param string $hook
193
     *
194
     * @return \WP_Mock\HookedCallback
195
     */
196
    public static function onActionAdded($hook)
197
    {
198
        return self::onHookAdded($hook, 'action');
3✔
199
    }
200

201
    /**
202
     * Alert the Event Manager that an action has been invoked.
203
     *
204
     * @param string $action
205
     */
206
    public static function invokeAction($action)
207
    {
208
        self::$event_manager->called($action);
1✔
209
    }
210

211
    public static function addFilter($hook)
212
    {
213
        self::addHook($hook, 'filter');
×
214
    }
215

216
    public static function addAction($hook)
217
    {
218
        self::addHook($hook, 'action');
×
219
    }
220

221
    public static function addHook($hook, $type = 'filter')
222
    {
223
        $type_name = "$type::$hook";
3✔
224
        self::$event_manager->called($type_name, 'callback');
3✔
225
    }
226

227
    /**
228
     * Adds an expectation that an action will be called during the test.
229
     *
230
     * @param string $action expected action
231
     * @returnv oid
232
     */
233
    public static function expectAction(string $action) : void
234
    {
235
        $intercept = Mockery::mock('intercept');
2✔
236
        $intercept->shouldReceive('intercepted')->atLeast()->once();
2✔
237
        $args = func_get_args();
2✔
238
        $args = count($args) > 1 ? array_slice($args, 1) : array( null );
2✔
239

240
        $mocked_action = self::onAction($action);
2✔
241
        $responder     = call_user_func_array(array( $mocked_action, 'with' ), $args);
2✔
242
        $responder->perform([$intercept, 'intercepted']);
2✔
243
    }
244

245
    /**
246
     * Adds an expectation that a filter will be applied during the test.
247
     *
248
     * @param string $filter expected filter
249
     * @return void
250
     */
251
    public static function expectFilter(string $filter) : void
252
    {
253
        $intercept = Mockery::mock('intercept');
3✔
254
        $intercept->shouldReceive('intercepted')->atLeast()->once()->andReturnUsing(function ($value) {
3✔
255
            return $value;
2✔
256
        });
3✔
257
        $args = func_num_args() > 1 ? array_slice(func_get_args(), 1) : array( null );
3✔
258

259
        $mocked_filter = self::onFilter($filter);
3✔
260
        $responder     = call_user_func_array(array( $mocked_filter, 'with' ), $args);
3✔
261
        $responder->reply(new WP_Mock\InvokedFilterValue(array( $intercept, 'intercepted' )));
3✔
262
    }
263

264
    /**
265
     * Asserts that all actions are called.
266
     *
267
     * @return void
268
     */
269
    public static function assertActionsCalled() : void
270
    {
271
        $allActionsCalled = self::$event_manager->allActionsCalled();
2✔
272
        $failed = implode(', ', self::$event_manager->expectedActions());
2✔
273
        PHPUnit\Framework\Assert::assertTrue($allActionsCalled, 'Method failed to invoke actions: ' . $failed);
2✔
274
    }
275

276
    /**
277
     * Asserts that all filters are called.
278
     *
279
     * @return void
280
     */
281
    public static function assertFiltersCalled() : void
282
    {
283
        $allFiltersCalled = self::$event_manager->allFiltersCalled();
2✔
284
        $failed           = implode(', ', self::$event_manager->expectedFilters());
2✔
285
        PHPUnit\Framework\Assert::assertTrue($allFiltersCalled, 'Method failed to invoke filters: ' . $failed);
2✔
286
    }
287

288
    /**
289
     * Adds an expectation that an action hook should be added.
290
     *
291
     * @param string $action the action hook name
292
     * @param string|callable-string|callable|Type $callback the callback that should be registered
293
     * @param int $priority the priority it should be registered at
294
     * @param int $args the number of arguments that should be allowed
295
     * @return void
296
     */
297
    public static function expectActionAdded(string $action, $callback, int $priority = 10, int $args = 1) : void
298
    {
299
        self::expectHookAdded('action', $action, $callback, $priority, $args);
3✔
300
    }
301

302
    /**
303
     * Adds an expectation that an action hook should not be added.
304
     *
305
     * @param string $action the action hook name
306
     * @param string|callable-string|callable|Type $callback the callback that should be registered
307
     * @param int $priority the priority it should be registered at
308
     * @param int $args the number of arguments that should be allowed
309
     * @return void
310
     */
311
    public static function expectActionNotAdded(string $action, $callback, int $priority = 10, int $args = 1) : void
312
    {
313
        self::expectHookNotAdded('action', $action, $callback, $priority, $args);
1✔
314
    }
315

316
    /**
317
     * Add an expectation that a filter hook should be added.
318
     *
319
     * @param string $filter the filter hook name
320
     * @param string|callable-string|callable|Type $callback the callback that should be registered
321
     * @param int $priority the priority it should be registered at
322
     * @param int $args the number of arguments that should be allowed
323
     * @return void
324
     */
325
    public static function expectFilterAdded(string $filter, $callback, int $priority = 10, int $args = 1) : void
326
    {
327
        self::expectHookAdded('filter', $filter, $callback, $priority, $args);
3✔
328
    }
329

330
    /**
331
     * Adds an expectation that a filter hook should not be added.
332
     *
333
     * @param string $filter the filter hook name
334
     * @param string|callable-string|callable|Type $callback the callback that should be registered
335
     * @param int $priority the priority it should be registered at
336
     * @param int $args the number of arguments that should be allowed
337
     * @return void
338
     */
339
    public static function expectFilterNotAdded(string $filter, $callback, int $priority = 10, int $args = 10) : void
340
    {
341
        self::expectHookNotAdded('filter', $filter, $callback, $priority, $args);
×
342
    }
343

344
    /**
345
     * Adds an expectation that a hook should be added.
346
     *
347
     * Based {@see Mockery\MockInterface::shouldReceive()}.
348
     *
349
     * @param string $type the type of hook being added ('action' or 'filter')
350
     * @param string $hook the hook name
351
     * @param string|callable-string|callable|Type $callback the callback that should be registered
352
     * @param int $priority the priority it should be registered at
353
     * @param int $args the number of arguments that should be allowed
354
     * @return void
355
     */
356
    public static function expectHookAdded(string $type, string $hook, $callback, int $priority = 10, int $args = 1) : void
357
    {
358
        $intercept = Mockery::mock('intercept');
3✔
359
        $intercept->shouldReceive('intercepted')->atLeast()->once();
3✔
360

361
        /** @var WP_Mock\HookedCallbackResponder $responder */
362
        $responder = self::onHookAdded($hook, $type)
3✔
363
            ->with($callback, $priority, $args);
3✔
364
        $responder->perform([$intercept, 'intercepted']);
3✔
365
    }
366

367
    /**
368
     * Adds an expectation that a hook should not be added.
369
     *
370
     * Based {@see Mockery\MockInterface::shouldNotReceive()}.
371
     *
372
     * @param string $type the type of hook being added ('action' or 'filter')
373
     * @param string $hook the hook name
374
     * @param string|callable-string|callable|Type $callback the callback that should be registered
375
     * @param int $priority the priority it should be registered at
376
     * @param int $args the number of arguments that should be allowed
377
     * @return void
378
     */
379
    public static function expectHookNotAdded(string $type, string $hook, $callback, int $priority = 10, int $args = 1) : void
380
    {
381
        $intercept = Mockery::mock('intercept');
1✔
382
        $intercept->shouldNotReceive('intercepted');
1✔
383

384
        /** @var WP_Mock\HookedCallbackResponder $responder */
385
        $responder = self::onHookAdded($hook, $type)
1✔
386
            ->with($callback, $priority, $args);
1✔
387
        $responder->perform([$intercept, 'intercepted']);
1✔
388
    }
389

390
    /**
391
     * Asserts that all hooks are added.
392
     *
393
     * @return void
394
     */
395
    public static function assertHooksAdded() : void
396
    {
397
        $allHooksAdded = self::$event_manager->allHooksAdded();
3✔
398
        $failed = implode(', ', self::$event_manager->expectedHooks());
3✔
399
        PHPUnit\Framework\Assert::assertTrue($allHooksAdded, 'Method failed to add hooks: ' . $failed);
3✔
400
    }
401

402
    /**
403
     * Mocks a WordPress API function.
404
     *
405
     * This function registers a mock object for a WordPress function and, if necessary, dynamically defines the function.
406
     *
407
     * Pass the function name as the first argument (e.g. `wp_remote_get()`) and pass in details about the expectations in the $args param.
408
     * The arguments have a few options for defining expectations about how the WordPress function should be used during a test.
409
     *
410
     * Currently, it accepts the following settings:
411
     *
412
     * - `times`: Defines expectations for the number of times a function should be called. The default is `0` or more times.
413
     *            To expect the function to be called an exact amount of times, set times to a non-negative numeric value.
414
     *            To specify that the function should be called a minimum number of times, use a string with the minimum followed by '+' (e.g. '3+' means 3 or more times).
415
     *            Append a '-' to indicate a maximum number of times a function should be called (e.g. '3-' means no more than 3 times).
416
     *            To indicate a range, use '-' between two numbers (e.g. '2-5' means at least 2 times and no more than 5 times).
417
     *
418
     * - `return`: Defines the value (if any) that the function should return.
419
     *             If you pass a `Closure` as the return value, the function will return whatever the closure's return value.
420
     *
421
     * - `return_in_order`: Use this if your function will be called multiple times in the test but needs to have different return values.
422
     *                      Set this to an array of return values. Each time the function is called, it will return the next value in the sequence until it reaches the last value, which will become the return value for all subsequent calls.
423
     *                      For example, if you are mocking `is_single()`, you can set `return_in_order` to `[false, true]`. The first time is_single() is called it will return false.
424
     *                      The second and all subsequent times it will return true. Setting this value overrides return, so if you set both, return will be ignored.
425
     *
426
     * - `return_arg`: Use this to specify that the function should return one of its arguments. `return_arg` should be the position of the argument in the arguments array, so `0` for the first argument, `1` for the second, etc.
427
     *                 You can also set this to true, which is equivalent to `0`. This will override both return and return_in_order.
428
     *
429
     * - `args`: Use this to set expectations about what the arguments passed to the function should be.
430
     *           This value should always be an array with the arguments in order.
431
     *           Like with `return`, if you use a `Closure`, its return value will be used to validate the argument expectations.
432
     *           WP_Mock has several helper functions to make this feature more flexible. There are static methods on the \WP_Mock\Functions class. They are:
433
     *           - {@see Functions::type($type)} Expects an argument of a certain type. This can be any core PHP data type (string, int, resource, callable, etc.) or any class or interface name.
434
     *           - {@see Functions::anyOf($values)} Expects the argument to be any value in the `$values` array.
435
     *           In addition to these helper functions, you can indicate that the argument can be any value of any type by using `*`.
436
     *           So, for example, if you are expecting `get_post_meta()` to be called, the `args` array might look something like this: `[$post->ID, 'some_meta_key', true]`.
437
     *
438
     * Returns the {@see Mockery\Expectation} object with the function expectations added.
439
     * It is possible to use Mockery methods to add expectations to the object returned, which will then be combined with any expectations that may have been passed as arguments.
440
     *
441
     * @param string $function function name
442
     * @param mixed[] $args optional arguments to set expectations
443
     * @return Mockery\Expectation
444
     * @throws InvalidArgumentException
445
     */
446
    public static function userFunction(string $function, array $args = [])
447
    {
448
        return self::$functionsManager->register($function, $args);
10✔
449
    }
450

451
    /**
452
     * A wrapper for {@see WP_Mock::userFunction()} that will simply set/override the return to be a function that echoes the value that its passed.
453
     *
454
     * For example, `esc_attr_e()` may need to be mocked, and it must echo some value.
455
     * {@see WP_Mock::echoFunction()} will set `esc_attr_e()` to echo the value its passed:
456
     *
457
     *    WP_Mock::echoFunction('esc_attr_e');
458
     *    esc_attr_e('some_value'); // echoes "some_value"
459
     *
460
     * @param string $function function name
461
     * @param mixed[]|scalar $args optional arguments
462
     * @return Mockery\Expectation
463
     * @throws InvalidArgumentException
464
     */
465
    public static function echoFunction(string $function, $args = [])
466
    {
467
        /** @var array<string, mixed> $args */
468
        $args = (array) $args;
1✔
469
        $args['return'] = function ($param) {
1✔
470
            echo $param;
1✔
471
        };
1✔
472

473
        return self::$functionsManager->register($function, $args);
1✔
474
    }
475

476
    /**
477
     * A wrapper for {@see WP_Mock::userFunction()} that will simply set/override the return to be a function that returns the value that its passed.
478
     *
479
     * For example, `esc_attr()` may need to be mocked, and it must return some value.
480
     * {@see WP_Mock::passthruFunction()} will set `esc_attr()` to return the value its passed:
481
     *
482
     *    WP_Mock::passthruFunction('esc_attr');
483
     *    echo esc_attr('some_value'); // echoes "some_value"
484
     *
485
     * @param string $function function name
486
     * @param mixed[]|scalar $args function arguments (optional)
487
     * @return Mockery\Expectation
488
     * @throws InvalidArgumentException
489
     */
490
    public static function passthruFunction(string $function, $args = [])
491
    {
492
        /** @var array<string, mixed> $args */
493
        $args = (array) $args;
1✔
494
        $args['return'] = function ($param) {
1✔
495
            return $param;
×
496
        };
1✔
497

498
        return self::$functionsManager->register($function, $args);
1✔
499
    }
500

501
    /**
502
     * Adds a function mock that aliases another callable.
503
     *
504
     * e.g.: WP_Mock::alias('wp_hash', 'md5');
505
     *
506
     * @param string|callable-string $function function to alias
507
     * @param string|callable-string $aliasFunction actual function
508
     * @param mixed[]|scalar $args optional arguments
509
     * @return Mockery\Expectation
510
     * @throws InvalidArgumentException
511
     */
512
    public static function alias(string $function, string $aliasFunction, $args = [])
513
    {
514
        /** @var array<string, mixed> $args */
515
        $args = (array) $args;
1✔
516

517
        if (is_callable($aliasFunction)) {
1✔
518
            $args['return'] = function () use ($aliasFunction) {
1✔
519
                return call_user_func_array($aliasFunction, func_get_args());
1✔
520
            };
1✔
521
        }
522

523
        return self::$functionsManager->register($function, $args);
1✔
524
    }
525

526
    /**
527
     * Generates a fuzzy object match expectation.
528
     *
529
     * This will let you fuzzy match objects based on their properties without needing to use the identical (===) operator.
530
     * This is helpful when the object being passed to a function is constructed inside the scope of the function being tested but where you want to make assertions on more than just the type of the object.
531
     *
532
     * @param object|array<mixed> $object
533
     * @return FuzzyObject
534
     * @throws MockeryException
535
     */
536
    public static function fuzzyObject($object): FuzzyObject
537
    {
538
        return new FuzzyObject($object);
3✔
539
    }
540

541
    /**
542
     * Gets the deprecated method listener instance.
543
     *
544
     * @return DeprecatedMethodListener
545
     */
546
    public static function getDeprecatedMethodListener(): DeprecatedMethodListener
547
    {
548
        return static::$deprecatedMethodListener;
2✔
549
    }
550

551
    /**
552
     * Adds an expectation that an action should be removed.
553
     *
554
     * @param string $action the action hook name
555
     * @param string|callable-string|callable|Type $callback the callable to be removed
556
     * @param int|Type|null $priority the priority it should be registered at
557
     *
558
     * @return ExpectationInterface
559
     * @throws InvalidArgumentException
560
     */
561
    public static function expectActionRemoved(string $action, $callback, $priority = null)
562
    {
563
        $args = [$action, $callback];
1✔
564
        if (!is_null($priority)) {
1✔
565
            $args[] = $priority;
1✔
566
        }
567

568
        return self::userFunction('remove_action', [
1✔
569
            'args'  => $args,
1✔
570
            'times' => 1,
1✔
571
        ]);
1✔
572
    }
573

574
    /**
575
     * Adds an expectation that an action should not be removed.
576
     *
577
     * @param string $action the action hook name
578
     * @param null|string|callable-string|callable|Type $callback the callable to be removed
579
     * @param int|Type|null $priority optional priority for the registered callback that is being removed
580
     *
581
     * @return ExpectationInterface
582
     * @throws InvalidArgumentException
583
     */
584
    public static function expectActionNotRemoved(string $action, $callback, $priority = null)
585
    {
586
        $args = [$action, $callback];
1✔
587
        if (!is_null($priority)) {
1✔
588
            $args[] = $priority;
1✔
589
        }
590

591
        return self::userFunction('remove_action',[
1✔
592
            'args'   => $args,
1✔
593
            'times'  => 0,
1✔
594
        ]);
1✔
595
    }
596

597
    /**
598
     * Adds an expectation that a filter should be removed.
599
     *
600
     * @param string $filter the filter name
601
     * @param string|callable-string|callable|Type $callback the callable to be removed
602
     * @param int|Type|null $priority the registered priority
603
     *
604
     * @return ExpectationInterface
605
     * @throws InvalidArgumentException
606
     */
607
    public static function expectFilterRemoved(string $filter, $callback, $priority = null)
608
    {
609
        $args = [$filter, $callback];
1✔
610
        if (!is_null($priority)) {
1✔
611
            $args[] = $priority;
1✔
612
        }
613

614
        return self::userFunction('remove_filter',[
1✔
615
            'args'   => $args,
1✔
616
            'times'  => 1,
1✔
617
        ]);
1✔
618
    }
619

620
    /**
621
     * Adds an expectation that a filter should not be removed.
622
     *
623
     * @param string $filter the filter name
624
     * @param null|string|callable-string|callable|Type $callback the callable to be removed
625
     * @param int|Type|null $priority optional priority for the registered callback that is being removed
626
     *
627
     * @return ExpectationInterface
628
     * @throws InvalidArgumentException
629
     */
630
    public static function expectFilterNotRemoved(string $filter, $callback, $priority = null)
631
    {
632
        $args = [$filter, $callback];
1✔
633
        if (!is_null($priority)) {
1✔
634
            $args[] = $priority;
1✔
635
        }
636

637
        return self::userFunction('remove_filter',[
1✔
638
            'args'   => $args,
1✔
639
            'times'  => 0,
1✔
640
        ]);
1✔
641
    }
642
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc