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

10up / wp_mock / 21057846478

16 Jan 2026 06:28AM UTC coverage: 66.538% (+0.4%) from 66.142%
21057846478

Pull #265

github

web-flow
Merge 4de613bcf into 446ea7083
Pull Request #265: Add `::setStrictModeForTest()` function

17 of 19 new or added lines in 1 file covered. (89.47%)

1 existing line in 1 file now uncovered.

519 of 780 relevant lines covered (66.54%)

2.82 hits per line

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

80.51
/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 WP_Mock\DeprecatedMethodListener;
12
use WP_Mock\Functions\Handler;
13
use WP_Mock\Matcher\FuzzyObject;
14
use Mockery\Matcher\Type;
15

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

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

29
    protected static $__bootstrapped = false;
30

31
    protected static $__use_patchwork = false;
32

33
    protected static $__strict_mode = false;
34

35
    /**
36
     * A record if strict mode was set individually for this test.
37
     *
38
     * Uses an associative array containing both method and setting as test-method-string:is-enabled-bool.
39
     *
40
     * @used-by self::setStrictModeForTest()
41
     * @used-by self::isStrictModeForTest()
42
     * @see self::strictMode()
43
     *
44
     * @var array<string, bool>
45
     */
46
    protected static $__strict_mode_for_individual_test = [];
47

48
    /** @var DeprecatedMethodListener */
49
    protected static $deprecatedMethodListener;
50

51
    /**
52
     * @param boolean $use_patchwork
53
     */
54
    public static function setUsePatchwork($use_patchwork)
55
    {
56
        if (! self::$__bootstrapped) {
×
57
            self::$__use_patchwork = (bool) $use_patchwork;
×
58
        }
59
    }
60

61
    public static function usingPatchwork()
62
    {
63
        return (bool) self::$__use_patchwork;
17✔
64
    }
65

66
    /**
67
     * Check whether strict mode is turned on
68
     *
69
     * @return bool
70
     */
71
    public static function strictMode()
72
    {
73
        return self::isStrictModeForTest() ?? (bool) self::$__strict_mode;
28✔
74
    }
75

76
    /**
77
     * Turns on strict mode
78
     */
79
    public static function activateStrictMode()
80
    {
81
        if (! self::$__bootstrapped) {
3✔
82
            self::$__strict_mode = true;
2✔
83
        }
84
    }
85

86
    /**
87
     * Sets strict mode on or off at runtime for an individual test.
88
     *
89
     * Records the config/preference for the individual test. Later this will be preferred over the default.
90
     *
91
     * @param bool $enabled
92
     * @throws Exception when the test case name cannot be determined.
93
     */
94
    public static function setStrictModeForTest(bool $enabled = true): void
95
    {
96
        $currentTestName = self::getCurrentlyRunningTestName();
4✔
97
        if(is_null($currentTestName)){
4✔
NEW
98
            throw new Exception('Failed to determine current test name');
×
99
        }
100
        self::$__strict_mode_for_individual_test = [$currentTestName => $enabled,];
4✔
101
    }
102

103
    /**
104
     * Check was strict mode configured individually for this test case.
105
     *
106
     * @see self::setStrictModeForTest()
107
     * @see self::$__strict_mode_for_individual_test
108
     *
109
     * @return ?bool `null` when not set, boolean preference when set.
110
     */
111
    protected static function isStrictModeForTest(): ?bool {
112
        if( empty( self::$__strict_mode_for_individual_test ) ) {
28✔
113
            return null;
24✔
114
        }
115

116
        $currentTestName = self::getCurrentlyRunningTestName();
6✔
117

118
        if(!is_null($currentTestName) && isset(self::$__strict_mode_for_individual_test[$currentTestName])) {
6✔
119
            return self::$__strict_mode_for_individual_test[$currentTestName];
4✔
120
        }
121
        
122
        // Reset the array since it is only relevant for the current test case run.
123
        self::$__strict_mode_for_individual_test = [];
2✔
124

125
        return null;
2✔
126
    }
127

128
    /**
129
     * Perform a backtrace to determine the currently running test.
130
     *
131
     * @return ?string of `class-string::method`
132
     */
133
    protected static function getCurrentlyRunningTestName(): ?string {
134
        $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
6✔
135

136
        /** @var array{class?:string, function?:string} $trace */
137
        foreach ($backtrace as $trace) {
6✔
138
            if (isset($trace['class']) && isset($trace['function'])) {
6✔
139
                // Check if this is a PHPUnit test class
140
                if (is_subclass_of($trace['class'], \PHPUnit\Framework\TestCase::class)) {
6✔
141
                    // Test method names start with 'test' or have @test annotation
142
                    if (strpos($trace['function'], 'test') === 0) {
6✔
143
                        return $trace['class'] . '::' . $trace['function'];
6✔
144
                    }
145
                }
146
            }
147
        }
148

NEW
149
        return null;
×
150
    }
151

152
    /**
153
     * Bootstraps WP_Mock.
154
     *
155
     * @return void
156
     */
157
    public static function bootstrap(): void
158
    {
159
        if (! self::$__bootstrapped) {
18✔
160
            self::$__bootstrapped = true;
17✔
161

162
            static::$deprecatedMethodListener = new DeprecatedMethodListener();
17✔
163

164
            require_once __DIR__ . '/WP_Mock/API/function-mocks.php';
17✔
165
            require_once __DIR__ . '/WP_Mock/API/constant-mocks.php';
17✔
166

167
            if (self::usingPatchwork()) {
17✔
168
                $patchwork_path  = 'antecedent/patchwork/Patchwork.php';
×
169
                $possible_locations = [
×
170
                    'vendor',
×
171
                    '../..',
×
172
                ];
×
173

174
                foreach ($possible_locations as $loc) {
×
175
                    $path = __DIR__ . "/../$loc/$patchwork_path";
×
176

177
                    if (file_exists($path)) {
×
178
                        break;
×
179
                    }
180
                }
181

182
                // Will cause a fatal error if patchwork can't be found
183
                require_once($path);
×
184
            }
185

186
            self::setUp();
17✔
187
        }
188
    }
189

190
    /**
191
     * Make sure Mockery doesn't have anything set up already.
192
     */
193
    public static function setUp(): void
194
    {
195
        if (self::$__bootstrapped) {
45✔
196
            \Mockery::close();
45✔
197

198
            self::$event_manager    = new \WP_Mock\EventManager();
45✔
199
            self::$functionsManager = new \WP_Mock\Functions();
45✔
200
        } else {
UNCOV
201
            self::bootstrap();
×
202
        }
203
    }
204

205
    /**
206
     * Tear down anything built up inside Mockery when we're ready to do so.
207
     */
208
    public static function tearDown(): void
209
    {
210
        self::$event_manager->flush();
×
211
        self::$functionsManager->flush();
×
212

213
        Mockery::close();
×
214
        Handler::cleanup();
×
215
    }
216

217
    /**
218
     * Fire a specific (mocked) callback when an apply_filters() call is used.
219
     *
220
     * @param string $filter
221
     *
222
     * @return \WP_Mock\Filter
223
     */
224
    public static function onFilter($filter)
225
    {
226
        self::$event_manager->called($filter, 'filter');
6✔
227
        return self::$event_manager->filter($filter);
6✔
228
    }
229

230
    /**
231
     * Fire a specific (mocked) callback when a do_action() call is used.
232
     *
233
     * @param string $action
234
     *
235
     * @return \WP_Mock\Action
236
     */
237
    public static function onAction($action)
238
    {
239
        return self::$event_manager->action($action);
2✔
240
    }
241

242
    /**
243
     * Get a filter or action added callback object
244
     *
245
     * @param string $hook
246
     * @param string $type
247
     *
248
     * @return \WP_Mock\HookedCallback
249
     */
250
    public static function onHookAdded($hook, $type = 'filter')
251
    {
252
        return self::$event_manager->callback($hook, $type);
5✔
253
    }
254

255
    /**
256
     * Get a filter added callback object
257
     *
258
     * @param string $hook
259
     *
260
     * @return \WP_Mock\HookedCallback
261
     */
262
    public static function onFilterAdded($hook)
263
    {
264
        return self::onHookAdded($hook, 'filter');
3✔
265
    }
266

267
    /**
268
     * Get an action added callback object
269
     *
270
     * @param string $hook
271
     *
272
     * @return \WP_Mock\HookedCallback
273
     */
274
    public static function onActionAdded($hook)
275
    {
276
        return self::onHookAdded($hook, 'action');
4✔
277
    }
278

279
    /**
280
     * Alert the Event Manager that an action has been invoked.
281
     *
282
     * @param string $action
283
     */
284
    public static function invokeAction($action)
285
    {
286
        self::$event_manager->called($action);
1✔
287
    }
288

289
    public static function addFilter($hook)
290
    {
291
        self::addHook($hook, 'filter');
×
292
    }
293

294
    public static function addAction($hook)
295
    {
296
        self::addHook($hook, 'action');
×
297
    }
298

299
    public static function addHook($hook, $type = 'filter')
300
    {
301
        $type_name = "$type::$hook";
4✔
302
        self::$event_manager->called($type_name, 'callback');
4✔
303
    }
304

305
    /**
306
     * Adds an expectation that an action will be called during the test.
307
     *
308
     * @param string $action expected action
309
     * @returnv oid
310
     */
311
    public static function expectAction(string $action) : void
312
    {
313
        $intercept = Mockery::mock('intercept');
2✔
314
        $intercept->shouldReceive('intercepted')->atLeast()->once();
2✔
315
        $args = func_get_args();
2✔
316
        $args = count($args) > 1 ? array_slice($args, 1) : array( null );
2✔
317

318
        $mocked_action = self::onAction($action);
2✔
319
        $responder     = call_user_func_array(array( $mocked_action, 'with' ), $args);
2✔
320
        $responder->perform([$intercept, 'intercepted']);
2✔
321
    }
322

323
    /**
324
     * Adds an expectation that a filter will be applied during the test.
325
     *
326
     * @param string $filter expected filter
327
     * @return void
328
     */
329
    public static function expectFilter(string $filter) : void
330
    {
331
        $intercept = Mockery::mock('intercept');
3✔
332
        $intercept->shouldReceive('intercepted')->atLeast()->once()->andReturnUsing(function ($value) {
3✔
333
            return $value;
2✔
334
        });
3✔
335
        $args = func_num_args() > 1 ? array_slice(func_get_args(), 1) : array( null );
3✔
336

337
        $mocked_filter = self::onFilter($filter);
3✔
338
        $responder     = call_user_func_array(array( $mocked_filter, 'with' ), $args);
3✔
339
        $responder->reply(new WP_Mock\InvokedFilterValue(array( $intercept, 'intercepted' )));
3✔
340
    }
341

342
    /**
343
     * Asserts that all actions are called.
344
     *
345
     * @return void
346
     */
347
    public static function assertActionsCalled() : void
348
    {
349
        $allActionsCalled = self::$event_manager->allActionsCalled();
2✔
350
        $failed = implode(', ', self::$event_manager->expectedActions());
2✔
351
        PHPUnit\Framework\Assert::assertTrue($allActionsCalled, 'Method failed to invoke actions: ' . $failed);
2✔
352
    }
353

354
    /**
355
     * Asserts that all filters are called.
356
     *
357
     * @return void
358
     */
359
    public static function assertFiltersCalled() : void
360
    {
361
        $allFiltersCalled = self::$event_manager->allFiltersCalled();
2✔
362
        $failed           = implode(', ', self::$event_manager->expectedFilters());
2✔
363
        PHPUnit\Framework\Assert::assertTrue($allFiltersCalled, 'Method failed to invoke filters: ' . $failed);
2✔
364
    }
365

366
    /**
367
     * Adds an expectation that an action hook should be added.
368
     *
369
     * @param string $action the action hook name
370
     * @param string|callable-string|callable|Type $callback the callback that should be registered
371
     * @param int $priority the priority it should be registered at
372
     * @param int $args the number of arguments that should be allowed
373
     * @return void
374
     */
375
    public static function expectActionAdded(string $action, $callback, int $priority = 10, int $args = 1) : void
376
    {
377
        self::expectHookAdded('action', $action, $callback, $priority, $args);
3✔
378
    }
379

380
    /**
381
     * Adds an expectation that an action hook should not be added.
382
     *
383
     * @param string $action the action hook name
384
     * @param string|callable-string|callable|Type $callback the callback that should be registered
385
     * @param int $priority the priority it should be registered at
386
     * @param int $args the number of arguments that should be allowed
387
     * @return void
388
     */
389
    public static function expectActionNotAdded(string $action, $callback, int $priority = 10, int $args = 1) : void
390
    {
391
        self::expectHookNotAdded('action', $action, $callback, $priority, $args);
1✔
392
    }
393

394
    /**
395
     * Add an expectation that a filter hook should be added.
396
     *
397
     * @param string $filter the filter hook name
398
     * @param string|callable-string|callable|Type $callback the callback that should be registered
399
     * @param int $priority the priority it should be registered at
400
     * @param int $args the number of arguments that should be allowed
401
     * @return void
402
     */
403
    public static function expectFilterAdded(string $filter, $callback, int $priority = 10, int $args = 1) : void
404
    {
405
        self::expectHookAdded('filter', $filter, $callback, $priority, $args);
3✔
406
    }
407

408
    /**
409
     * Adds an expectation that a filter hook should not be added.
410
     *
411
     * @param string $filter the filter hook name
412
     * @param string|callable-string|callable|Type $callback the callback that should be registered
413
     * @param int $priority the priority it should be registered at
414
     * @param int $args the number of arguments that should be allowed
415
     * @return void
416
     */
417
    public static function expectFilterNotAdded(string $filter, $callback, int $priority = 10, int $args = 10) : void
418
    {
419
        self::expectHookNotAdded('filter', $filter, $callback, $priority, $args);
×
420
    }
421

422
    /**
423
     * Adds an expectation that a hook should be added.
424
     *
425
     * Based {@see Mockery\MockInterface::shouldReceive()}.
426
     *
427
     * @param string $type the type of hook being added ('action' or 'filter')
428
     * @param string $hook the hook name
429
     * @param string|callable-string|callable|Type $callback the callback that should be registered
430
     * @param int $priority the priority it should be registered at
431
     * @param int $args the number of arguments that should be allowed
432
     * @return void
433
     */
434
    public static function expectHookAdded(string $type, string $hook, $callback, int $priority = 10, int $args = 1) : void
435
    {
436
        $intercept = Mockery::mock('intercept');
3✔
437
        $intercept->shouldReceive('intercepted')->atLeast()->once();
3✔
438

439
        /** @var WP_Mock\HookedCallbackResponder $responder */
440
        $responder = self::onHookAdded($hook, $type)
3✔
441
            ->with($callback, $priority, $args);
3✔
442
        $responder->perform([$intercept, 'intercepted']);
3✔
443
    }
444

445
    /**
446
     * Adds an expectation that a hook should not be added.
447
     *
448
     * Based {@see Mockery\MockInterface::shouldNotReceive()}.
449
     *
450
     * @param string $type the type of hook being added ('action' or 'filter')
451
     * @param string $hook the hook name
452
     * @param string|callable-string|callable|Type $callback the callback that should be registered
453
     * @param int $priority the priority it should be registered at
454
     * @param int $args the number of arguments that should be allowed
455
     * @return void
456
     */
457
    public static function expectHookNotAdded(string $type, string $hook, $callback, int $priority = 10, int $args = 1) : void
458
    {
459
        $intercept = Mockery::mock('intercept');
1✔
460
        $intercept->shouldNotReceive('intercepted');
1✔
461

462
        /** @var WP_Mock\HookedCallbackResponder $responder */
463
        $responder = self::onHookAdded($hook, $type)
1✔
464
            ->with($callback, $priority, $args);
1✔
465
        $responder->perform([$intercept, 'intercepted']);
1✔
466
    }
467

468
    /**
469
     * Asserts that all hooks are added.
470
     *
471
     * @return void
472
     */
473
    public static function assertHooksAdded() : void
474
    {
475
        $allHooksAdded = self::$event_manager->allHooksAdded();
3✔
476
        $failed = implode(', ', self::$event_manager->expectedHooks());
3✔
477
        PHPUnit\Framework\Assert::assertTrue($allHooksAdded, 'Method failed to add hooks: ' . $failed);
3✔
478
    }
479

480
    /**
481
     * Mocks a WordPress API function.
482
     *
483
     * This function registers a mock object for a WordPress function and, if necessary, dynamically defines the function.
484
     *
485
     * Pass the function name as the first argument (e.g. `wp_remote_get()`) and pass in details about the expectations in the $args param.
486
     * The arguments have a few options for defining expectations about how the WordPress function should be used during a test.
487
     *
488
     * Currently, it accepts the following settings:
489
     *
490
     * - `times`: Defines expectations for the number of times a function should be called. The default is `0` or more times.
491
     *            To expect the function to be called an exact amount of times, set times to a non-negative numeric value.
492
     *            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).
493
     *            Append a '-' to indicate a maximum number of times a function should be called (e.g. '3-' means no more than 3 times).
494
     *            To indicate a range, use '-' between two numbers (e.g. '2-5' means at least 2 times and no more than 5 times).
495
     *
496
     * - `return`: Defines the value (if any) that the function should return.
497
     *             If you pass a `Closure` as the return value, the function will return whatever the closure's return value.
498
     *
499
     * - `return_in_order`: Use this if your function will be called multiple times in the test but needs to have different return values.
500
     *                      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.
501
     *                      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.
502
     *                      The second and all subsequent times it will return true. Setting this value overrides return, so if you set both, return will be ignored.
503
     *
504
     * - `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.
505
     *                 You can also set this to true, which is equivalent to `0`. This will override both return and return_in_order.
506
     *
507
     * - `args`: Use this to set expectations about what the arguments passed to the function should be.
508
     *           This value should always be an array with the arguments in order.
509
     *           Like with `return`, if you use a `Closure`, its return value will be used to validate the argument expectations.
510
     *           WP_Mock has several helper functions to make this feature more flexible. There are static methods on the \WP_Mock\Functions class. They are:
511
     *           - {@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.
512
     *           - {@see Functions::anyOf($values)} Expects the argument to be any value in the `$values` array.
513
     *           In addition to these helper functions, you can indicate that the argument can be any value of any type by using `*`.
514
     *           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]`.
515
     *
516
     * Returns the {@see Mockery\Expectation} object with the function expectations added.
517
     * 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.
518
     *
519
     * @param string $function function name
520
     * @param mixed[] $args optional arguments to set expectations
521
     * @return Mockery\Expectation
522
     * @throws InvalidArgumentException
523
     */
524
    public static function userFunction(string $function, array $args = [])
525
    {
526
        return self::$functionsManager->register($function, $args);
8✔
527
    }
528

529
    /**
530
     * 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.
531
     *
532
     * For example, `esc_attr_e()` may need to be mocked, and it must echo some value.
533
     * {@see WP_Mock::echoFunction()} will set `esc_attr_e()` to echo the value its passed:
534
     *
535
     *    WP_Mock::echoFunction('esc_attr_e');
536
     *    esc_attr_e('some_value'); // echoes "some_value"
537
     *
538
     * @param string $function function name
539
     * @param mixed[]|scalar $args optional arguments
540
     * @return Mockery\Expectation
541
     * @throws InvalidArgumentException
542
     */
543
    public static function echoFunction(string $function, $args = [])
544
    {
545
        /** @var array<string, mixed> $args */
546
        $args = (array) $args;
1✔
547
        $args['return'] = function ($param) {
1✔
548
            echo $param;
1✔
549
        };
1✔
550

551
        return self::$functionsManager->register($function, $args);
1✔
552
    }
553

554
    /**
555
     * 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.
556
     *
557
     * For example, `esc_attr()` may need to be mocked, and it must return some value.
558
     * {@see WP_Mock::passthruFunction()} will set `esc_attr()` to return the value its passed:
559
     *
560
     *    WP_Mock::passthruFunction('esc_attr');
561
     *    echo esc_attr('some_value'); // echoes "some_value"
562
     *
563
     * @param string $function function name
564
     * @param mixed[]|scalar $args function arguments (optional)
565
     * @return Mockery\Expectation
566
     * @throws InvalidArgumentException
567
     */
568
    public static function passthruFunction(string $function, $args = [])
569
    {
570
        /** @var array<string, mixed> $args */
571
        $args = (array) $args;
1✔
572
        $args['return'] = function ($param) {
1✔
573
            return $param;
×
574
        };
1✔
575

576
        return self::$functionsManager->register($function, $args);
1✔
577
    }
578

579
    /**
580
     * Adds a function mock that aliases another callable.
581
     *
582
     * e.g.: WP_Mock::alias('wp_hash', 'md5');
583
     *
584
     * @param string|callable-string $function function to alias
585
     * @param string|callable-string $aliasFunction actual function
586
     * @param mixed[]|scalar $args optional arguments
587
     * @return Mockery\Expectation
588
     * @throws InvalidArgumentException
589
     */
590
    public static function alias(string $function, string $aliasFunction, $args = [])
591
    {
592
        /** @var array<string, mixed> $args */
593
        $args = (array) $args;
1✔
594

595
        if (is_callable($aliasFunction)) {
1✔
596
            $args['return'] = function () use ($aliasFunction) {
1✔
597
                return call_user_func_array($aliasFunction, func_get_args());
1✔
598
            };
1✔
599
        }
600

601
        return self::$functionsManager->register($function, $args);
1✔
602
    }
603

604
    /**
605
     * Generates a fuzzy object match expectation.
606
     *
607
     * This will let you fuzzy match objects based on their properties without needing to use the identical (===) operator.
608
     * 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.
609
     *
610
     * @param object|array<mixed> $object
611
     * @return FuzzyObject
612
     * @throws MockeryException
613
     */
614
    public static function fuzzyObject($object): FuzzyObject
615
    {
616
        return new FuzzyObject($object);
3✔
617
    }
618

619
    /**
620
     * Gets the deprecated method listener instance.
621
     *
622
     * @return DeprecatedMethodListener
623
     */
624
    public static function getDeprecatedMethodListener(): DeprecatedMethodListener
625
    {
626
        return static::$deprecatedMethodListener;
2✔
627
    }
628
}
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