| 148 |
lars |
1 |
.. index::
|
|
|
2 |
single: Argument Validation
|
|
|
3 |
|
|
|
4 |
Argument Validation
|
|
|
5 |
===================
|
|
|
6 |
|
|
|
7 |
The arguments passed to the ``with()`` declaration when setting up an
|
|
|
8 |
expectation determine the criteria for matching method calls to expectations.
|
|
|
9 |
Thus, we can setup up many expectations for a single method, each
|
|
|
10 |
differentiated by the expected arguments. Such argument matching is done on a
|
|
|
11 |
"best fit" basis. This ensures explicit matches take precedence over
|
|
|
12 |
generalised matches.
|
|
|
13 |
|
|
|
14 |
An explicit match is merely where the expected argument and the actual
|
|
|
15 |
argument are easily equated (i.e. using ``===`` or ``==``). More generalised
|
|
|
16 |
matches are possible using regular expressions, class hinting and the
|
|
|
17 |
available generic matchers. The purpose of generalised matchers is to allow
|
|
|
18 |
arguments be defined in non-explicit terms, e.g. ``Mockery::any()`` passed to
|
|
|
19 |
``with()`` will match **any** argument in that position.
|
|
|
20 |
|
|
|
21 |
Mockery's generic matchers do not cover all possibilities but offers optional
|
|
|
22 |
support for the Hamcrest library of matchers. Hamcrest is a PHP port of the
|
|
|
23 |
similarly named Java library (which has been ported also to Python, Erlang,
|
|
|
24 |
etc). By using Hamcrest, Mockery does not need to duplicate Hamcrest's already
|
|
|
25 |
impressive utility which itself promotes a natural English DSL.
|
|
|
26 |
|
|
|
27 |
The examples below show Mockery matchers and their Hamcrest equivalent, if there
|
|
|
28 |
is one. Hamcrest uses functions (no namespacing).
|
|
|
29 |
|
|
|
30 |
.. note::
|
|
|
31 |
|
|
|
32 |
If you don't wish to use the global Hamcrest functions, they are all exposed
|
|
|
33 |
through the ``\Hamcrest\Matchers`` class as well, as static methods. Thus,
|
|
|
34 |
``identicalTo($arg)`` is the same as ``\Hamcrest\Matchers::identicalTo($arg)``
|
|
|
35 |
|
|
|
36 |
The most common matcher is the ``with()`` matcher:
|
|
|
37 |
|
|
|
38 |
.. code-block:: php
|
|
|
39 |
|
|
|
40 |
$mock = \Mockery::mock('MyClass');
|
|
|
41 |
$mock->shouldReceive('foo')
|
|
|
42 |
->with(1):
|
|
|
43 |
|
|
|
44 |
It tells mockery that it should receive a call to the ``foo`` method with the
|
|
|
45 |
integer ``1`` as an argument. In cases like this, Mockery first tries to match
|
|
|
46 |
the arguments using ``===`` (identical) comparison operator. If the argument is
|
|
|
47 |
a primitive, and if it fails the identical comparison, Mockery does a fallback
|
|
|
48 |
to the ``==`` (equals) comparison operator.
|
|
|
49 |
|
|
|
50 |
When matching objects as arguments, Mockery only does the strict ``===``
|
|
|
51 |
comparison, which means only the same ``$object`` will match:
|
|
|
52 |
|
|
|
53 |
.. code-block:: php
|
|
|
54 |
|
|
|
55 |
$object = new stdClass();
|
|
|
56 |
$mock = \Mockery::mock('MyClass');
|
|
|
57 |
$mock->shouldReceive("foo")
|
|
|
58 |
->with($object);
|
|
|
59 |
|
|
|
60 |
// Hamcrest equivalent
|
|
|
61 |
$mock->shouldReceive("foo")
|
|
|
62 |
->with(identicalTo($object));
|
|
|
63 |
|
|
|
64 |
A different instance of ``stdClass`` will **not** match.
|
|
|
65 |
|
|
|
66 |
.. note::
|
|
|
67 |
|
|
|
68 |
The ``Mockery\Matcher\MustBe`` matcher has been deprecated.
|
|
|
69 |
|
|
|
70 |
If we need a loose comparison of objects, we can do that using Hamcrest's
|
|
|
71 |
``equalTo`` matcher:
|
|
|
72 |
|
|
|
73 |
.. code-block:: php
|
|
|
74 |
|
|
|
75 |
$mock->shouldReceive("foo")
|
|
|
76 |
->with(equalTo(new stdClass));
|
|
|
77 |
|
|
|
78 |
In cases when we don't care about the type, or the value of an argument, just
|
|
|
79 |
that any argument is present, we use ``any()``:
|
|
|
80 |
|
|
|
81 |
.. code-block:: php
|
|
|
82 |
|
|
|
83 |
$mock = \Mockery::mock('MyClass');
|
|
|
84 |
$mock->shouldReceive("foo")
|
|
|
85 |
->with(\Mockery::any());
|
|
|
86 |
|
|
|
87 |
// Hamcrest equivalent
|
|
|
88 |
$mock->shouldReceive("foo")
|
|
|
89 |
->with(anything())
|
|
|
90 |
|
|
|
91 |
Anything and everything passed in this argument slot is passed unconstrained.
|
|
|
92 |
|
|
|
93 |
Validating Types and Resources
|
|
|
94 |
------------------------------
|
|
|
95 |
|
|
|
96 |
The ``type()`` matcher accepts any string which can be attached to ``is_`` to
|
|
|
97 |
form a valid type check.
|
|
|
98 |
|
|
|
99 |
To match any PHP resource, we could do the following:
|
|
|
100 |
|
|
|
101 |
.. code-block:: php
|
|
|
102 |
|
|
|
103 |
$mock = \Mockery::mock('MyClass');
|
|
|
104 |
$mock->shouldReceive("foo")
|
|
|
105 |
->with(\Mockery::type('resource'));
|
|
|
106 |
|
|
|
107 |
// Hamcrest equivalents
|
|
|
108 |
$mock->shouldReceive("foo")
|
|
|
109 |
->with(resourceValue());
|
|
|
110 |
$mock->shouldReceive("foo")
|
|
|
111 |
->with(typeOf('resource'));
|
|
|
112 |
|
|
|
113 |
It will return a ``true`` from an ``is_resource()`` call, if the provided
|
|
|
114 |
argument to the method is a PHP resource. For example, ``\Mockery::type('float')``
|
|
|
115 |
or Hamcrest's ``floatValue()`` and ``typeOf('float')`` checks use ``is_float()``,
|
|
|
116 |
and ``\Mockery::type('callable')`` or Hamcrest's ``callable()`` uses
|
|
|
117 |
``is_callable()``.
|
|
|
118 |
|
|
|
119 |
The ``type()`` matcher also accepts a class or interface name to be used in an
|
|
|
120 |
``instanceof`` evaluation of the actual argument. Hamcrest uses ``anInstanceOf()``.
|
|
|
121 |
|
|
|
122 |
A full list of the type checkers is available at
|
|
|
123 |
`php.net <http://www.php.net/manual/en/ref.var.php>`_ or browse Hamcrest's function
|
|
|
124 |
list in
|
|
|
125 |
`the Hamcrest code <https://github.com/hamcrest/hamcrest-php/blob/master/hamcrest/Hamcrest.php>`_.
|
|
|
126 |
|
|
|
127 |
.. _argument-validation-complex-argument-validation:
|
|
|
128 |
|
|
|
129 |
Complex Argument Validation
|
|
|
130 |
---------------------------
|
|
|
131 |
|
|
|
132 |
If we want to perform a complex argument validation, the ``on()`` matcher is
|
|
|
133 |
invaluable. It accepts a closure (anonymous function) to which the actual
|
|
|
134 |
argument will be passed.
|
|
|
135 |
|
|
|
136 |
.. code-block:: php
|
|
|
137 |
|
|
|
138 |
$mock = \Mockery::mock('MyClass');
|
|
|
139 |
$mock->shouldReceive("foo")
|
|
|
140 |
->with(\Mockery::on(closure));
|
|
|
141 |
|
|
|
142 |
If the closure evaluates to (i.e. returns) boolean ``true`` then the argument is
|
|
|
143 |
assumed to have matched the expectation.
|
|
|
144 |
|
|
|
145 |
.. code-block:: php
|
|
|
146 |
|
|
|
147 |
$mock = \Mockery::mock('MyClass');
|
|
|
148 |
|
|
|
149 |
$mock->shouldReceive('foo')
|
|
|
150 |
->with(\Mockery::on(function ($argument) {
|
|
|
151 |
if ($argument % 2 == 0) {
|
|
|
152 |
return true;
|
|
|
153 |
}
|
|
|
154 |
return false;
|
|
|
155 |
}));
|
|
|
156 |
|
|
|
157 |
$mock->foo(4); // matches the expectation
|
|
|
158 |
$mock->foo(3); // throws a NoMatchingExpectationException
|
|
|
159 |
|
|
|
160 |
.. note::
|
|
|
161 |
|
|
|
162 |
There is no Hamcrest version of the ``on()`` matcher.
|
|
|
163 |
|
|
|
164 |
We can also perform argument validation by passing a closure to ``withArgs()``
|
|
|
165 |
method. The closure will receive all arguments passed in the call to the expected
|
|
|
166 |
method and if it evaluates (i.e. returns) to boolean ``true``, then the list of
|
|
|
167 |
arguments is assumed to have matched the expectation:
|
|
|
168 |
|
|
|
169 |
.. code-block:: php
|
|
|
170 |
|
|
|
171 |
$mock = \Mockery::mock('MyClass');
|
|
|
172 |
$mock->shouldReceive("foo")
|
|
|
173 |
->withArgs(closure);
|
|
|
174 |
|
|
|
175 |
The closure can also handle optional parameters, so if an optional parameter is
|
|
|
176 |
missing in the call to the expected method, it doesn't necessary means that the
|
|
|
177 |
list of arguments doesn't match the expectation.
|
|
|
178 |
|
|
|
179 |
.. code-block:: php
|
|
|
180 |
|
|
|
181 |
$closure = function ($odd, $even, $sum = null) {
|
|
|
182 |
$result = ($odd % 2 != 0) && ($even % 2 == 0);
|
|
|
183 |
if (!is_null($sum)) {
|
|
|
184 |
return $result && ($odd + $even == $sum);
|
|
|
185 |
}
|
|
|
186 |
return $result;
|
|
|
187 |
};
|
|
|
188 |
|
|
|
189 |
$mock = \Mockery::mock('MyClass');
|
|
|
190 |
$mock->shouldReceive('foo')->withArgs($closure);
|
|
|
191 |
|
|
|
192 |
$mock->foo(1, 2); // It matches the expectation: the optional argument is not needed
|
|
|
193 |
$mock->foo(1, 2, 3); // It also matches the expectation: the optional argument pass the validation
|
|
|
194 |
$mock->foo(1, 2, 4); // It doesn't match the expectation: the optional doesn't pass the validation
|
|
|
195 |
|
|
|
196 |
.. note::
|
|
|
197 |
|
|
|
198 |
In previous versions, Mockery's ``with()`` would attempt to do a pattern
|
|
|
199 |
matching against the arguments, attempting to use the argument as a
|
|
|
200 |
regular expression. Over time this proved to be not such a great idea, so
|
|
|
201 |
we removed this functionality, and have introduced ``Mockery::pattern()``
|
|
|
202 |
instead.
|
|
|
203 |
|
|
|
204 |
If we would like to match an argument against a regular expression, we can use
|
|
|
205 |
the ``\Mockery::pattern()``:
|
|
|
206 |
|
|
|
207 |
.. code-block:: php
|
|
|
208 |
|
|
|
209 |
$mock = \Mockery::mock('MyClass');
|
|
|
210 |
$mock->shouldReceive('foo')
|
|
|
211 |
->with(\Mockery::pattern('/^foo/'));
|
|
|
212 |
|
|
|
213 |
// Hamcrest equivalent
|
|
|
214 |
$mock->shouldReceive('foo')
|
|
|
215 |
->with(matchesPattern('/^foo/'));
|
|
|
216 |
|
|
|
217 |
The ``ducktype()`` matcher is an alternative to matching by class type:
|
|
|
218 |
|
|
|
219 |
.. code-block:: php
|
|
|
220 |
|
|
|
221 |
$mock = \Mockery::mock('MyClass');
|
|
|
222 |
$mock->shouldReceive('foo')
|
|
|
223 |
->with(\Mockery::ducktype('foo', 'bar'));
|
|
|
224 |
|
|
|
225 |
It matches any argument which is an object containing the provided list of
|
|
|
226 |
methods to call.
|
|
|
227 |
|
|
|
228 |
.. note::
|
|
|
229 |
|
|
|
230 |
There is no Hamcrest version of the ``ducktype()`` matcher.
|
|
|
231 |
|
|
|
232 |
Capturing Arguments
|
|
|
233 |
-------------------
|
|
|
234 |
|
|
|
235 |
If we want to perform multiple validations on a single argument, the ``capture``
|
|
|
236 |
matcher provides a streamlined alternative to using the ``on()`` matcher.
|
|
|
237 |
It accepts a variable which the actual argument will be assigned.
|
|
|
238 |
|
|
|
239 |
.. code-block:: php
|
|
|
240 |
|
|
|
241 |
$mock = \Mockery::mock('MyClass');
|
|
|
242 |
$mock->shouldReceive("foo")
|
|
|
243 |
->with(\Mockery::capture($bar));
|
|
|
244 |
|
|
|
245 |
This will assign *any* argument passed to ``foo`` to the local ``$bar`` variable to
|
|
|
246 |
then perform additional validation using assertions.
|
|
|
247 |
|
|
|
248 |
.. note::
|
|
|
249 |
|
|
|
250 |
The ``capture`` matcher always evaluates to ``true``. As such, we should always
|
|
|
251 |
perform additional argument validation.
|
|
|
252 |
|
|
|
253 |
Additional Argument Matchers
|
|
|
254 |
----------------------------
|
|
|
255 |
|
|
|
256 |
The ``not()`` matcher matches any argument which is not equal or identical to
|
|
|
257 |
the matcher's parameter:
|
|
|
258 |
|
|
|
259 |
.. code-block:: php
|
|
|
260 |
|
|
|
261 |
$mock = \Mockery::mock('MyClass');
|
|
|
262 |
$mock->shouldReceive('foo')
|
|
|
263 |
->with(\Mockery::not(2));
|
|
|
264 |
|
|
|
265 |
// Hamcrest equivalent
|
|
|
266 |
$mock->shouldReceive('foo')
|
|
|
267 |
->with(not(2));
|
|
|
268 |
|
|
|
269 |
``anyOf()`` matches any argument which equals any one of the given parameters:
|
|
|
270 |
|
|
|
271 |
.. code-block:: php
|
|
|
272 |
|
|
|
273 |
$mock = \Mockery::mock('MyClass');
|
|
|
274 |
$mock->shouldReceive('foo')
|
|
|
275 |
->with(\Mockery::anyOf(1, 2));
|
|
|
276 |
|
|
|
277 |
// Hamcrest equivalent
|
|
|
278 |
$mock->shouldReceive('foo')
|
|
|
279 |
->with(anyOf(1,2));
|
|
|
280 |
|
|
|
281 |
``notAnyOf()`` matches any argument which is not equal or identical to any of
|
|
|
282 |
the given parameters:
|
|
|
283 |
|
|
|
284 |
.. code-block:: php
|
|
|
285 |
|
|
|
286 |
$mock = \Mockery::mock('MyClass');
|
|
|
287 |
$mock->shouldReceive('foo')
|
|
|
288 |
->with(\Mockery::notAnyOf(1, 2));
|
|
|
289 |
|
|
|
290 |
.. note::
|
|
|
291 |
|
|
|
292 |
There is no Hamcrest version of the ``notAnyOf()`` matcher.
|
|
|
293 |
|
|
|
294 |
``subset()`` matches any argument which is any array containing the given array
|
|
|
295 |
subset:
|
|
|
296 |
|
|
|
297 |
.. code-block:: php
|
|
|
298 |
|
|
|
299 |
$mock = \Mockery::mock('MyClass');
|
|
|
300 |
$mock->shouldReceive('foo')
|
|
|
301 |
->with(\Mockery::subset(array(0 => 'foo')));
|
|
|
302 |
|
|
|
303 |
This enforces both key naming and values, i.e. both the key and value of each
|
|
|
304 |
actual element is compared.
|
|
|
305 |
|
|
|
306 |
.. note::
|
|
|
307 |
|
|
|
308 |
There is no Hamcrest version of this functionality, though Hamcrest can check
|
|
|
309 |
a single entry using ``hasEntry()`` or ``hasKeyValuePair()``.
|
|
|
310 |
|
|
|
311 |
``contains()`` matches any argument which is an array containing the listed
|
|
|
312 |
values:
|
|
|
313 |
|
|
|
314 |
.. code-block:: php
|
|
|
315 |
|
|
|
316 |
$mock = \Mockery::mock('MyClass');
|
|
|
317 |
$mock->shouldReceive('foo')
|
|
|
318 |
->with(\Mockery::contains(value1, value2));
|
|
|
319 |
|
|
|
320 |
The naming of keys is ignored.
|
|
|
321 |
|
|
|
322 |
``hasKey()`` matches any argument which is an array containing the given key
|
|
|
323 |
name:
|
|
|
324 |
|
|
|
325 |
.. code-block:: php
|
|
|
326 |
|
|
|
327 |
$mock = \Mockery::mock('MyClass');
|
|
|
328 |
$mock->shouldReceive('foo')
|
|
|
329 |
->with(\Mockery::hasKey(key));
|
|
|
330 |
|
|
|
331 |
``hasValue()`` matches any argument which is an array containing the given
|
|
|
332 |
value:
|
|
|
333 |
|
|
|
334 |
.. code-block:: php
|
|
|
335 |
|
|
|
336 |
$mock = \Mockery::mock('MyClass');
|
|
|
337 |
$mock->shouldReceive('foo')
|
|
|
338 |
->with(\Mockery::hasValue(value));
|