Skip to content
Newer
Older
100644 204 lines (149 sloc) 5.77 KB
834db0d @jnthn Add synopsis to README, as suggested by masak++.
authored
1 SYNOPSIS
2
3 use Test;
4 use Test::Mock;
5
6 plan 2;
7
8 class Foo {
9 method lol() { 'rofl' }
10 method wtf() { 'oh ffs' }
11 }
12
13 my $x = mocked(Foo);
14
15 $x.lol();
16 $x.lol();
17
18 check-mock($x,
19 *.called('lol', times => 2),
20 *.never-called('wtf'),
21 );
22
23 DETAILS
24
e54aae7 @jnthn Initial commit of the basic library and the README.
authored
25 Test::Mock is a module that works alongside the standard Test module to
26 help you write tests when you want to verify what methods are called on
27 an object, while still having calls to undefined methods die.
28
29 You get started just as normal with the test file, but also add a use
30 statement for Test::Mock.
31
32 use Test;
33 use Test::Mock;
34
35 plan 2;
36
37 Imagine we have some class Foo:
38
39 class Foo {
40 method lol() { 'rofl' }
41 method wtf() { 'oh ffs' }
42 }
43
44 We then arrange to have a mocked instance of this class. This means that
45 instead of calls to lol and wtf actually resulting in the methods being
46 invoked, it simply logs the invocations.
47
48 my $x = mocked(Foo);
49
50 We can then take the actions that should result in some method calls.
68232cb @jnthn Update README to explain with.
authored
51 Here we just make them directly, but you'd probably pass the mock to
52 other bits of code that will make calls on it.
e54aae7 @jnthn Initial commit of the basic library and the README.
authored
53
54 $x.lol();
55 $x.lol();
56
57 When you're done, you assert that the things you expected to happen
58 actually happened.
59
60 check-mock($x,
61 *.called('lol', times => 2),
62 *.never-called('wtf'),
63 );
64
68232cb @jnthn Update README to explain with.
authored
65 And it's as easy as that. Of course, you may also be interested to check
66 that the arguments passed to the mocked method were as expected. For our
67 second example, here's a class representing one of my favorite places.
e54aae7 @jnthn Initial commit of the basic library and the README.
authored
68
68232cb @jnthn Update README to explain with.
authored
69 class Pub {
70 method order_beer($pints) { }
71 method throw($what) { }
72 }
73
74 We'll also declare a couple of other classes:
75
76 class Glass { }
77 class Party { }
78
79 Our test file would have started with the same boilerplate - use Test and
80 Test::Mock, and set a plan. We then produce a mock instance of Pub:
81
82 my $p = mocked(Pub);
83
84 And do our stuff:
85
86 $p.throw(Party.new);
87 $p.order_beer(2);
88 $p.order_beer(1);
89
90 After our excruciatingly low on beer party, we can now do some checks. Of
91 course, we ordered beer twice, so we can check this as before:
92
93 check-mock($p,
94 *.called('order_beer', times => 2),
95
96 But what if we wanted to check the arguments passed to the method? In that
97 case, you can simply pass along the parameter "with". We may pass a Capture
98 here, which contains the exact arguments we expected to be passed; this will
99 be tested against the actual passed Capture for equivalance.
100
101 *.called('order_beer', times => 1, with => \(1)),
102 *.called('order_beer', times => 1, with => \(2)),
103 *.never-called('order_beer', with => \(10)),
104
105 That's going to cover some cases, but what if we wanted to check if things of
106 the correct type were passed? In that case, write a Signature literal, and the
107 args Capture will be smart-matched against it, which conveniently happens to
108 check if the Capture could have bound to this Signature.
109
110 *.called('throw', with => :(Party)),
111 *.never-called('throw', with => :(Glass)),
112
113 Of course, now the gloves are off: if you have a Signature you can do all kinds
114 of matching, with constraints and sub-signatures. Here's an easy but not so
115 creative example (I need at least 3 pints to be creative...)
116
117 *.called('order_beer', times => 2, with => :($ where { $^n < 10 })),
118 *.never-called('order_beer', with => :($ where { $^n >= 10 })),
119
120 And if all that isn't enough, since we just smart-match against anything else
121 you may pass as the with argument, you may also pass a block that takes a
122 Capture as a parameter and implement whatever fancier checks you may wish to.
123
079c9e7 @jnthn Document :%returning parameter with another example.
authored
124 In some cases, just logging method calls on your mock may not be enough; you
125 may wish them to return some fake data from the method call. For example, we
126 may have a yak shaving class that we dependency-inject with a yak provider
127 and a yak shaver.
128
129 class Yak {
130 has $.shaved;
131 }
132
133 class Shaver {
134 method shave($yak) {
135 ...
136 }
137 }
138
139 class YakStore {
140 method get-all-yaks() {
141 ...
142 }
143 }
144
145 class YakShaving {
146 has $!yak-store;
147 has $!yak-shaver;
148
149 method proccess() {
150 for $!yak-store.get-all-yaks() -> $yak {
151 unless $yak.shaved {
152 $!yak-shaver.shave($yak);
153 }
154 }
155 }
156 }
157
158 We want to check that our the shave method from the Shaver class is only
159 invoked for yaks that need shaving. We set up our mock of the Shaver class
160 just as normal:
161
162 my $shaver = mocked(Shaver);
163
164 However, for the Yak store we want to provide some fake yaks in various
165 states of shavenness.
166
167 my $store = mocked(YakStore, returning => {
168 get-all-yaks => (Yak.new(:!shaved), Yak.new(:shaved), Yak.new(:!shaved))
169 });
170
171 Now we can inject our mocks to the YakShaving class and and get it to do
172 it's thing.
173
174 my $yaktivity = YakShaving.new(
175 yak-store => $store,
176 yak-shaver => $shaver
177 );
178 $yaktivity.proccess();
179
180 And finally, it's time to write our tests. We expect just one call on the
181 store:
182
183 check-mock($store,
184 *.called('get-all-yaks', times => 1)
185 );
186
187 On the shaver, we expect two calls in total to the shave method with yaks
188 that are unshaven, and no calls at all with shaven yaks.
189
190 check-mock($shaver,
191 *.called('shave', times => 2, with => :($ where { !$^y.shaved })),
192 *.never-called('shave', with => :($ where { $^y.shaved }))
193 );
194
195 This is the first example where we're really made good use of mock testing;
196 if absolutely every object involved in the test is mocked, then you'd not be
197 testing any of the actual real code. Of course, being able to do this easily
198 somewhat depends on good de-coupled code design, where objects are given
199 instances of other objects to work on rather than directly instantiating
200 objects of other classes.
201
202 Feature requests, bug reports and patches on this module are welcome; use
203 the GitHub issues tracker.
Something went wrong with that request. Please try again.