argument_validation.rst 10 KB

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