123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338 |
- .. index::
- single: Argument Validation
- Argument Validation
- ===================
- The arguments passed to the ``with()`` declaration when setting up an
- expectation determine the criteria for matching method calls to expectations.
- Thus, we can setup up many expectations for a single method, each
- differentiated by the expected arguments. Such argument matching is done on a
- "best fit" basis. This ensures explicit matches take precedence over
- generalised matches.
- An explicit match is merely where the expected argument and the actual
- argument are easily equated (i.e. using ``===`` or ``==``). More generalised
- matches are possible using regular expressions, class hinting and the
- available generic matchers. The purpose of generalised matchers is to allow
- arguments be defined in non-explicit terms, e.g. ``Mockery::any()`` passed to
- ``with()`` will match **any** argument in that position.
- Mockery's generic matchers do not cover all possibilities but offers optional
- support for the Hamcrest library of matchers. Hamcrest is a PHP port of the
- similarly named Java library (which has been ported also to Python, Erlang,
- etc). By using Hamcrest, Mockery does not need to duplicate Hamcrest's already
- impressive utility which itself promotes a natural English DSL.
- The examples below show Mockery matchers and their Hamcrest equivalent, if there
- is one. Hamcrest uses functions (no namespacing).
- .. note::
- If you don't wish to use the global Hamcrest functions, they are all exposed
- through the ``\Hamcrest\Matchers`` class as well, as static methods. Thus,
- ``identicalTo($arg)`` is the same as ``\Hamcrest\Matchers::identicalTo($arg)``
- The most common matcher is the ``with()`` matcher:
- .. code-block:: php
- $mock = \Mockery::mock('MyClass');
- $mock->shouldReceive('foo')
- ->with(1):
- It tells mockery that it should receive a call to the ``foo`` method with the
- integer ``1`` as an argument. In cases like this, Mockery first tries to match
- the arguments using ``===`` (identical) comparison operator. If the argument is
- a primitive, and if it fails the identical comparison, Mockery does a fallback
- to the ``==`` (equals) comparison operator.
- When matching objects as arguments, Mockery only does the strict ``===``
- comparison, which means only the same ``$object`` will match:
- .. code-block:: php
- $object = new stdClass();
- $mock = \Mockery::mock('MyClass');
- $mock->shouldReceive("foo")
- ->with($object);
- // Hamcrest equivalent
- $mock->shouldReceive("foo")
- ->with(identicalTo($object));
- A different instance of ``stdClass`` will **not** match.
- .. note::
- The ``Mockery\Matcher\MustBe`` matcher has been deprecated.
- If we need a loose comparison of objects, we can do that using Hamcrest's
- ``equalTo`` matcher:
- .. code-block:: php
- $mock->shouldReceive("foo")
- ->with(equalTo(new stdClass));
- In cases when we don't care about the type, or the value of an argument, just
- that any argument is present, we use ``any()``:
- .. code-block:: php
- $mock = \Mockery::mock('MyClass');
- $mock->shouldReceive("foo")
- ->with(\Mockery::any());
- // Hamcrest equivalent
- $mock->shouldReceive("foo")
- ->with(anything())
- Anything and everything passed in this argument slot is passed unconstrained.
- Validating Types and Resources
- ------------------------------
- The ``type()`` matcher accepts any string which can be attached to ``is_`` to
- form a valid type check.
- To match any PHP resource, we could do the following:
- .. code-block:: php
- $mock = \Mockery::mock('MyClass');
- $mock->shouldReceive("foo")
- ->with(\Mockery::type('resource'));
- // Hamcrest equivalents
- $mock->shouldReceive("foo")
- ->with(resourceValue());
- $mock->shouldReceive("foo")
- ->with(typeOf('resource'));
- It will return a ``true`` from an ``is_resource()`` call, if the provided
- argument to the method is a PHP resource. For example, ``\Mockery::type('float')``
- or Hamcrest's ``floatValue()`` and ``typeOf('float')`` checks use ``is_float()``,
- and ``\Mockery::type('callable')`` or Hamcrest's ``callable()`` uses
- ``is_callable()``.
- The ``type()`` matcher also accepts a class or interface name to be used in an
- ``instanceof`` evaluation of the actual argument. Hamcrest uses ``anInstanceOf()``.
- A full list of the type checkers is available at
- `php.net <http://www.php.net/manual/en/ref.var.php>`_ or browse Hamcrest's function
- list in
- `the Hamcrest code <https://github.com/hamcrest/hamcrest-php/blob/master/hamcrest/Hamcrest.php>`_.
- .. _argument-validation-complex-argument-validation:
- Complex Argument Validation
- ---------------------------
- If we want to perform a complex argument validation, the ``on()`` matcher is
- invaluable. It accepts a closure (anonymous function) to which the actual
- argument will be passed.
- .. code-block:: php
- $mock = \Mockery::mock('MyClass');
- $mock->shouldReceive("foo")
- ->with(\Mockery::on(closure));
- If the closure evaluates to (i.e. returns) boolean ``true`` then the argument is
- assumed to have matched the expectation.
- .. code-block:: php
- $mock = \Mockery::mock('MyClass');
- $mock->shouldReceive('foo')
- ->with(\Mockery::on(function ($argument) {
- if ($argument % 2 == 0) {
- return true;
- }
- return false;
- }));
- $mock->foo(4); // matches the expectation
- $mock->foo(3); // throws a NoMatchingExpectationException
- .. note::
- There is no Hamcrest version of the ``on()`` matcher.
- We can also perform argument validation by passing a closure to ``withArgs()``
- method. The closure will receive all arguments passed in the call to the expected
- method and if it evaluates (i.e. returns) to boolean ``true``, then the list of
- arguments is assumed to have matched the expectation:
- .. code-block:: php
- $mock = \Mockery::mock('MyClass');
- $mock->shouldReceive("foo")
- ->withArgs(closure);
- The closure can also handle optional parameters, so if an optional parameter is
- missing in the call to the expected method, it doesn't necessary means that the
- list of arguments doesn't match the expectation.
- .. code-block:: php
- $closure = function ($odd, $even, $sum = null) {
- $result = ($odd % 2 != 0) && ($even % 2 == 0);
- if (!is_null($sum)) {
- return $result && ($odd + $even == $sum);
- }
- return $result;
- };
- $mock = \Mockery::mock('MyClass');
- $mock->shouldReceive('foo')->withArgs($closure);
- $mock->foo(1, 2); // It matches the expectation: the optional argument is not needed
- $mock->foo(1, 2, 3); // It also matches the expectation: the optional argument pass the validation
- $mock->foo(1, 2, 4); // It doesn't match the expectation: the optional doesn't pass the validation
- .. note::
- In previous versions, Mockery's ``with()`` would attempt to do a pattern
- matching against the arguments, attempting to use the argument as a
- regular expression. Over time this proved to be not such a great idea, so
- we removed this functionality, and have introduced ``Mockery::pattern()``
- instead.
- If we would like to match an argument against a regular expression, we can use
- the ``\Mockery::pattern()``:
- .. code-block:: php
- $mock = \Mockery::mock('MyClass');
- $mock->shouldReceive('foo')
- ->with(\Mockery::pattern('/^foo/'));
- // Hamcrest equivalent
- $mock->shouldReceive('foo')
- ->with(matchesPattern('/^foo/'));
- The ``ducktype()`` matcher is an alternative to matching by class type:
- .. code-block:: php
- $mock = \Mockery::mock('MyClass');
- $mock->shouldReceive('foo')
- ->with(\Mockery::ducktype('foo', 'bar'));
- It matches any argument which is an object containing the provided list of
- methods to call.
- .. note::
- There is no Hamcrest version of the ``ducktype()`` matcher.
- Capturing Arguments
- -------------------
- If we want to perform multiple validations on a single argument, the ``capture``
- matcher provides a streamlined alternative to using the ``on()`` matcher.
- It accepts a variable which the actual argument will be assigned.
- .. code-block:: php
- $mock = \Mockery::mock('MyClass');
- $mock->shouldReceive("foo")
- ->with(\Mockery::capture($bar));
- This will assign *any* argument passed to ``foo`` to the local ``$bar`` variable to
- then perform additional validation using assertions.
- .. note::
- The ``capture`` matcher always evaluates to ``true``. As such, we should always
- perform additional argument validation.
- Additional Argument Matchers
- ----------------------------
- The ``not()`` matcher matches any argument which is not equal or identical to
- the matcher's parameter:
- .. code-block:: php
- $mock = \Mockery::mock('MyClass');
- $mock->shouldReceive('foo')
- ->with(\Mockery::not(2));
- // Hamcrest equivalent
- $mock->shouldReceive('foo')
- ->with(not(2));
- ``anyOf()`` matches any argument which equals any one of the given parameters:
- .. code-block:: php
- $mock = \Mockery::mock('MyClass');
- $mock->shouldReceive('foo')
- ->with(\Mockery::anyOf(1, 2));
- // Hamcrest equivalent
- $mock->shouldReceive('foo')
- ->with(anyOf(1,2));
- ``notAnyOf()`` matches any argument which is not equal or identical to any of
- the given parameters:
- .. code-block:: php
- $mock = \Mockery::mock('MyClass');
- $mock->shouldReceive('foo')
- ->with(\Mockery::notAnyOf(1, 2));
- .. note::
- There is no Hamcrest version of the ``notAnyOf()`` matcher.
- ``subset()`` matches any argument which is any array containing the given array
- subset:
- .. code-block:: php
- $mock = \Mockery::mock('MyClass');
- $mock->shouldReceive('foo')
- ->with(\Mockery::subset(array(0 => 'foo')));
- This enforces both key naming and values, i.e. both the key and value of each
- actual element is compared.
- .. note::
- There is no Hamcrest version of this functionality, though Hamcrest can check
- a single entry using ``hasEntry()`` or ``hasKeyValuePair()``.
- ``contains()`` matches any argument which is an array containing the listed
- values:
- .. code-block:: php
- $mock = \Mockery::mock('MyClass');
- $mock->shouldReceive('foo')
- ->with(\Mockery::contains(value1, value2));
- The naming of keys is ignored.
- ``hasKey()`` matches any argument which is an array containing the given key
- name:
- .. code-block:: php
- $mock = \Mockery::mock('MyClass');
- $mock->shouldReceive('foo')
- ->with(\Mockery::hasKey(key));
- ``hasValue()`` matches any argument which is an array containing the given
- value:
- .. code-block:: php
- $mock = \Mockery::mock('MyClass');
- $mock->shouldReceive('foo')
- ->with(\Mockery::hasValue(value));
|