| 148 |
lars |
1 |
.. index::
|
|
|
2 |
single: Reference; Spies
|
|
|
3 |
|
|
|
4 |
Spies
|
|
|
5 |
=====
|
|
|
6 |
|
|
|
7 |
Spies are a type of test doubles, but they differ from stubs or mocks in that,
|
|
|
8 |
that the spies record any interaction between the spy and the System Under Test
|
|
|
9 |
(SUT), and allow us to make assertions against those interactions after the fact.
|
|
|
10 |
|
|
|
11 |
Creating a spy means we don't have to set up expectations for every method call
|
|
|
12 |
the double might receive during the test, some of which may not be relevant to
|
|
|
13 |
the current test. A spy allows us to make assertions about the calls we care
|
|
|
14 |
about for this test only, reducing the chances of over-specification and making
|
|
|
15 |
our tests more clear.
|
|
|
16 |
|
|
|
17 |
Spies also allow us to follow the more familiar Arrange-Act-Assert or
|
|
|
18 |
Given-When-Then style within our tests. With mocks, we have to follow a less
|
|
|
19 |
familiar style, something along the lines of Arrange-Expect-Act-Assert, where
|
|
|
20 |
we have to tell our mocks what to expect before we act on the SUT, then assert
|
|
|
21 |
that those expectations where met:
|
|
|
22 |
|
|
|
23 |
.. code-block:: php
|
|
|
24 |
|
|
|
25 |
// arrange
|
|
|
26 |
$mock = \Mockery::mock('MyDependency');
|
|
|
27 |
$sut = new MyClass($mock);
|
|
|
28 |
|
|
|
29 |
// expect
|
|
|
30 |
$mock->shouldReceive('foo')
|
|
|
31 |
->once()
|
|
|
32 |
->with('bar');
|
|
|
33 |
|
|
|
34 |
// act
|
|
|
35 |
$sut->callFoo();
|
|
|
36 |
|
|
|
37 |
// assert
|
|
|
38 |
\Mockery::close();
|
|
|
39 |
|
|
|
40 |
Spies allow us to skip the expect part and move the assertion to after we have
|
|
|
41 |
acted on the SUT, usually making our tests more readable:
|
|
|
42 |
|
|
|
43 |
.. code-block:: php
|
|
|
44 |
|
|
|
45 |
// arrange
|
|
|
46 |
$spy = \Mockery::spy('MyDependency');
|
|
|
47 |
$sut = new MyClass($spy);
|
|
|
48 |
|
|
|
49 |
// act
|
|
|
50 |
$sut->callFoo();
|
|
|
51 |
|
|
|
52 |
// assert
|
|
|
53 |
$spy->shouldHaveReceived()
|
|
|
54 |
->foo()
|
|
|
55 |
->with('bar');
|
|
|
56 |
|
|
|
57 |
On the other hand, spies are far less restrictive than mocks, meaning tests are
|
|
|
58 |
usually less precise, as they let us get away with more. This is usually a
|
|
|
59 |
good thing, they should only be as precise as they need to be, but while spies
|
|
|
60 |
make our tests more intent-revealing, they do tend to reveal less about the
|
|
|
61 |
design of the SUT. If we're having to setup lots of expectations for a mock,
|
|
|
62 |
in lots of different tests, our tests are trying to tell us something - the SUT
|
|
|
63 |
is doing too much and probably should be refactored. We don't get this with
|
|
|
64 |
spies, they simply ignore the calls that aren't relevant to them.
|
|
|
65 |
|
|
|
66 |
Another downside to using spies is debugging. When a mock receives a call that
|
|
|
67 |
it wasn't expecting, it immediately throws an exception (failing fast), giving
|
|
|
68 |
us a nice stack trace or possibly even invoking our debugger. With spies, we're
|
|
|
69 |
simply asserting calls were made after the fact, so if the wrong calls were made,
|
|
|
70 |
we don't have quite the same just in time context we have with the mocks.
|
|
|
71 |
|
|
|
72 |
Finally, if we need to define a return value for our test double, we can't do
|
|
|
73 |
that with a spy, only with a mock object.
|
|
|
74 |
|
|
|
75 |
.. note::
|
|
|
76 |
|
|
|
77 |
This documentation page is an adaption of the blog post titled
|
|
|
78 |
`"Mockery Spies" <https://davedevelopment.co.uk/2014/10/09/mockery-spies.html>`_,
|
|
|
79 |
published by Dave Marshall on his blog. Dave is the original author of spies
|
|
|
80 |
in Mockery.
|
|
|
81 |
|
|
|
82 |
Spies Reference
|
|
|
83 |
---------------
|
|
|
84 |
|
|
|
85 |
To verify that a method was called on a spy, we use the ``shouldHaveReceived()``
|
|
|
86 |
method:
|
|
|
87 |
|
|
|
88 |
.. code-block:: php
|
|
|
89 |
|
|
|
90 |
$spy->shouldHaveReceived('foo');
|
|
|
91 |
|
|
|
92 |
To verify that a method was **not** called on a spy, we use the
|
|
|
93 |
``shouldNotHaveReceived()`` method:
|
|
|
94 |
|
|
|
95 |
.. code-block:: php
|
|
|
96 |
|
|
|
97 |
$spy->shouldNotHaveReceived('foo');
|
|
|
98 |
|
|
|
99 |
We can also do argument matching with spies:
|
|
|
100 |
|
|
|
101 |
.. code-block:: php
|
|
|
102 |
|
|
|
103 |
$spy->shouldHaveReceived('foo')
|
|
|
104 |
->with('bar');
|
|
|
105 |
|
|
|
106 |
Argument matching is also possible by passing in an array of arguments to
|
|
|
107 |
match:
|
|
|
108 |
|
|
|
109 |
.. code-block:: php
|
|
|
110 |
|
|
|
111 |
$spy->shouldHaveReceived('foo', ['bar']);
|
|
|
112 |
|
|
|
113 |
Although when verifying a method was not called, the argument matching can only
|
|
|
114 |
be done by supplying the array of arguments as the 2nd argument to the
|
|
|
115 |
``shouldNotHaveReceived()`` method:
|
|
|
116 |
|
|
|
117 |
.. code-block:: php
|
|
|
118 |
|
|
|
119 |
$spy->shouldNotHaveReceived('foo', ['bar']);
|
|
|
120 |
|
|
|
121 |
This is due to Mockery's internals.
|
|
|
122 |
|
|
|
123 |
Finally, when expecting calls that should have been received, we can also verify
|
|
|
124 |
the number of calls:
|
|
|
125 |
|
|
|
126 |
.. code-block:: php
|
|
|
127 |
|
|
|
128 |
$spy->shouldHaveReceived('foo')
|
|
|
129 |
->with('bar')
|
|
|
130 |
->twice();
|
|
|
131 |
|
|
|
132 |
Alternative shouldReceive syntax
|
|
|
133 |
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
134 |
|
|
|
135 |
As of Mockery 1.0.0, we support calling methods as we would call any PHP method,
|
|
|
136 |
and not as string arguments to Mockery ``should*`` methods.
|
|
|
137 |
|
|
|
138 |
In cases of spies, this only applies to the ``shouldHaveReceived()`` method:
|
|
|
139 |
|
|
|
140 |
.. code-block:: php
|
|
|
141 |
|
|
|
142 |
$spy->shouldHaveReceived()
|
|
|
143 |
->foo('bar');
|
|
|
144 |
|
|
|
145 |
We can set expectation on number of calls as well:
|
|
|
146 |
|
|
|
147 |
.. code-block:: php
|
|
|
148 |
|
|
|
149 |
$spy->shouldHaveReceived()
|
|
|
150 |
->foo('bar')
|
|
|
151 |
->twice();
|
|
|
152 |
|
|
|
153 |
Unfortunately, due to limitations we can't support the same syntax for the
|
|
|
154 |
``shouldNotHaveReceived()`` method.
|