forked from 10up/wp_mock
/
WP_Mock.php
371 lines (339 loc) · 12.7 KB
/
WP_Mock.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
<?php
/**
* WP_Mock
*
* LICENSE
*
* Copyright 2013 10up and other contributors
* http://10up.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @package WP_Mock
* @copyright Copyright (c) 2013 10up (http://10up.com)
* @license MIT License
*/
use WP_Mock\Matcher\FuzzyObject;
class WP_Mock {
/**
* @var \WP_Mock\EventManager
*/
protected static $event_manager;
/**
* @var \WP_Mock\Functions
*/
protected static $function_manager;
protected static $__bootstrapped = false;
protected static $__use_patchwork = false;
/**
* @param boolean $use_patchwork
*/
public static function setUsePatchwork( $use_patchwork ) {
if ( ! self::$__bootstrapped ) {
self::$__use_patchwork = (bool) $use_patchwork;
}
}
public static function usingPatchwork() {
return (bool) self::$__use_patchwork;
}
/**
* Bootstrap WP_Mock
*/
public static function bootstrap() {
if ( ! self::$__bootstrapped ) {
self::$__bootstrapped = true;
require_once __DIR__ . '/WP_Mock/API/function-mocks.php';
require_once __DIR__ . '/WP_Mock/API/constant-mocks.php';
if ( self::usingPatchwork() ) {
$possible_locations = array(
'/vendor',
'/../..',
);
$patchwork_path = 'antecedent/patchwork/Patchwork.php';
foreach ( $possible_locations as $loc ) {
$path = __DIR__ . "$loc/$patchwork_path";
if ( file_exists( $path ) ) {
break;
}
}
// Will cause a fatal error if patchwork can't be found
require_once( $path );
}
self::setUp();
}
}
/**
* Make sure Mockery doesn't have anything set up already.
*/
public static function setUp() {
if ( self::$__bootstrapped ) {
\Mockery::close();
self::$event_manager = new \WP_Mock\EventManager();
self::$function_manager = new \WP_Mock\Functions();
} else {
self::bootstrap();
}
}
/**
* Tear down anything built up inside Mockery when we're ready to do so.
*/
public static function tearDown() {
self::$event_manager->flush();
self::$function_manager->flush();
\Mockery::close();
\WP_Mock\Handler::cleanup();
}
/**
* Fire a specific (mocked) callback when an apply_filters() call is used.
*
* @param string $filter
*
* @return \WP_Mock\Filter
*/
public static function onFilter( $filter ) {
return self::$event_manager->filter( $filter );
}
/**
* Fire a specific (mocked) callback when a do_action() call is used.
*
* @param string $action
*
* @return \WP_Mock\Action
*/
public static function onAction( $action ) {
return self::$event_manager->action( $action );
}
/**
* Get a filter or action added callback object
*
* @param string $hook
* @param string $type
*
* @return \WP_Mock\HookedCallback
*/
public static function onHookAdded( $hook, $type = 'filter' ) {
return self::$event_manager->callback( $hook, $type );
}
/**
* Get a filter added callback object
*
* @param string $hook
*
* @return \WP_Mock\HookedCallback
*/
public static function onFilterAdded( $hook ) {
return self::onHookAdded( $hook, 'filter' );
}
/**
* Get an action added callback object
*
* @param string $hook
*
* @return \WP_Mock\HookedCallback
*/
public static function onActionAdded( $hook ) {
return self::onHookAdded( $hook, 'action' );
}
/**
* Alert the Event Manager that an action has been invoked.
*
* @param string $action
*/
public static function invokeAction( $action ) {
self::$event_manager->called( $action );
}
public static function addFilter( $hook ) {
self::addHook( $hook, 'filter' );
}
public static function addAction( $hook ) {
self::addHook( $hook, 'action' );
}
public static function addHook( $hook, $type = 'filter' ) {
$type_name = "$type::$hook";
self::$event_manager->called( $type_name, 'callback' );
}
/**
* Set up the expectation that an action will be called during the test.
*
* Mock a WordPress action, regardless of the parameters used. This call merely
* verifies that the action is invoked by the tested method.
*
* @param string $action Action we expect the method to call
*/
public static function expectAction( $action ) {
$intercept = \Mockery::mock( 'intercept' );
$intercept->shouldReceive( 'intercepted' )->atLeast()->once();
$args = func_get_args();
$args = count( $args ) > 1 ? array_slice( $args, 1 ) : array( null );
$action = self::onAction( $action );
$responder = call_user_func_array( array( $action, 'with' ), $args );
$responder->perform( array( $intercept, 'intercepted' ) );
}
public static function assertActionsCalled() {
if ( ! self::$event_manager->allActionsCalled() ) {
$failed = implode( ', ', self::$event_manager->expectedActions() );
throw new PHPUnit_Framework_ExpectationFailedException( 'Method failed to invoke actions: ' . $failed, null );
}
}
/**
* Add an expectation that an action should be added
*
* Really just a wrapper function for expectHookAdded()
*
* @param string $action The action name
* @param string $callback The callback that should be registered
* @param int $priority The priority it should be registered at
* @param int $args The number of arguments that should be allowed
*/
public static function expectActionAdded( $action, $callback, $priority = 10, $args = 1 ) {
self::expectHookAdded( 'action', $action, $callback, $priority, $args );
}
/**
* Add an expectation that a filter should be added
*
* Really just a wrapper function for expectHookAdded()
*
* @param string $filter The action name
* @param string $callback The callback that should be registered
* @param int $priority The priority it should be registered at
* @param int $args The number of arguments that should be allowed
*/
public static function expectFilterAdded( $filter, $callback, $priority = 10, $args = 1 ) {
self::expectHookAdded( 'filter', $filter, $callback, $priority, $args );
}
/**
* Add an expectation that a hook should be added
*
* @param string $type The type of hook being added
* @param string $action The action name
* @param string $callback The callback that should be registered
* @param int $priority The priority it should be registered at
* @param int $args The number of arguments that should be allowed
*/
public static function expectHookAdded( $type, $action, $callback, $priority = 10, $args = 1 ) {
$intercept = \Mockery::mock( 'intercept' );
$intercept->shouldReceive( 'intercepted' )->atLeast()->once();
/** @var WP_Mock\HookedCallbackResponder $responder */
$responder = self::onHookAdded( $action, $type )
->with( $callback, $priority, $args );
$responder->perform( array( $intercept, 'intercepted' ) );
}
public static function assertHooksAdded() {
if ( ! self:: $event_manager->allHooksAdded() ) {
$failed = implode( ', ', self::$event_manager->expectedHooks() );
throw new PHPUnit_Framework_ExpectationFailedException( 'Method failed to add hooks: ' . $failed, null );
}
}
/**
* Mock a WordPress API function
*
* This function registers a mock object for a WordPress function and, if
* necessary, dynamically defines the function. Pass the function name as
* the first argument (e.g. wp_remote_get) and pass in details about the
* expectations in the $arguments array. The arguments array has a few
* options for defining expectations about how the WordPress function should
* be used during a test. Currently, it accepts the following settings:
*
* - times: Defines expectations for the number of times a function should
* be called. The default is 0 or more times. To expect the function to be
* called an exact amount of times, set times to a non-negative numeric
* value. 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). Append a '-' to indicate a maximum number of
* times a function should be called (e.g. '3-' means no more than 3 times)
* To indicate a range, use '-' between two numbers (e.g. '2-5' means at
* least 2 times and no more than 5 times)
* - return: Defines the value (if any) that the function should return. If
* you pass a Closure as the return value, the function will return
* whatever the Closure's return value.
* - return_in_order: Use this if your function will be called multiple
* times in the test but needs to have different return values. 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. For
* example, if I am mocking is_single(), I can set return_in_order to
* array( false, true ). The first time is_single() is called it will
* return false. The second and all subsequent times it will return true.
* Setting this value overrides return, so if you set both, return will be
* ignored.
* - 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.
* You can also set this to true, which is equivalent to 0. This will
* override both return and return_in_order.
* - args: Use this to set expectations about what the arguments passed to
* the function should be. This value should always be an array with the
* arguments in order. Like with return, if you use a Closure, its return
* value will be used to validate the argument expectations. WP_Mock has
* several helper functions to make this feature more flexible. The are
* static methods on the \WP_Mock\Functions class. They are:
* - 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.
* - Functions::anyOf( $values ) Expects the argument to be any value in
* the $values array
* In addition to these helper functions, you can indicate that the
* argument can be any value of any type by using '*'. So, for example, if
* I am expecting get_post_meta to be called, the args array might look
* something like this:
* array( $post->ID, 'some_meta_key', true )
*
* @param string $function_name
* @param array $arguments
*/
public static function wpFunction( $function_name, $arguments = array() ) {
self::$function_manager->register( $function_name, $arguments );
}
/**
* A wrapper for wpFunction that will simply set/override the return to be
* a function that returns the value that its passed. For example, esc_attr
* may need to be mocked, and it must return some value. wpPassthruFunction
* will set esc_attr to return the value its passed.
*
* \WP_Mock::wpPassthruFunction( 'esc_attr' );
* echo esc_attr( 'some_value' ); // echoes "some_value"
*
* @param string $function_name
* @param array $arguments
*/
public static function wpPassthruFunction( $function_name, $arguments = array() ) {
$arguments = (array) $arguments;
$arguments['return'] = function ( $param ) {
return $param;
};
self::$function_manager->register( $function_name, $arguments );
}
/**
* Generate a fuzzy object match expectation
*
* This will let you fuzzy match objects based on their properties without
* needing to use the identical (===) operator. 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.
*
* @param $thing
*
* @return FuzzyObject
*/
public static function fuzzyObject( $thing ) {
return new FuzzyObject( $thing );
}
}