Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 271 lines (205 sloc) 9.286 kb
8879661 @brynary Add Code Climate badge
brynary authored
1 # RSpec Mocks [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/rspec/rspec-mocks)
f9f8418 @dchelimsky words
dchelimsky authored
2
f4bd883 @dchelimsky rdoc (and a rename or two)
dchelimsky authored
3 rspec-mocks is a test-double framework for rspec with support for method stubs,
4 fakes, and message expectations on generated test-doubles and real objects
5 alike.
f9f8418 @dchelimsky words
dchelimsky authored
6
6084658 @dchelimsky links to docs
dchelimsky authored
7 ## Install
f9f8418 @dchelimsky words
dchelimsky authored
8
6084658 @dchelimsky links to docs
dchelimsky authored
9 gem install rspec # for rspec-core, rspec-expectations, rspec-mocks
10 gem install rspec-mocks # for rspec-mocks only
d5a1da7 @spicycode Initial jeweler repository creation
spicycode authored
11
c186d12 @dchelimsky move overview to Readme (where it belongs)
dchelimsky authored
12 ## Test Doubles
13
14 A Test Double is an object that stands in for a real object in a test.
15 RSpec creates test doubles that support method stubs and message
16 expectations.
17
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
18 ```ruby
f86db3e @icambron removed extra indentation in readme
icambron authored
19 book = double("book")
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
20 ```
c186d12 @dchelimsky move overview to Readme (where it belongs)
dchelimsky authored
21
f26af00 @dchelimsky words
dchelimsky authored
22 ## Method Stubs
23
0a824d2 @dchelimsky more README
dchelimsky authored
24 A method stub is an implementation that returns a pre-determined value. Method
25 stubs can be declared on test doubles or real objects using the same syntax.
26 rspec-mocks supports 3 forms for declaring method stubs:
c186d12 @dchelimsky move overview to Readme (where it belongs)
dchelimsky authored
27
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
28 ```ruby
f86db3e @icambron removed extra indentation in readme
icambron authored
29 book.stub(:title) { "The RSpec Book" }
30 book.stub(:title => "The RSpec Book")
31 book.stub(:title).and_return("The RSpec Book")
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
32 ```
c186d12 @dchelimsky move overview to Readme (where it belongs)
dchelimsky authored
33
0a824d2 @dchelimsky more README
dchelimsky authored
34 You can also use this shortcut, which creates a test double and declares a
35 method stub in one statement:
c186d12 @dchelimsky move overview to Readme (where it belongs)
dchelimsky authored
36
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
37 ```ruby
f86db3e @icambron removed extra indentation in readme
icambron authored
38 book = double("book", :title => "The RSpec Book")
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
39 ```
c186d12 @dchelimsky move overview to Readme (where it belongs)
dchelimsky authored
40
0a824d2 @dchelimsky more README
dchelimsky authored
41 The first argment is a name, which is used for documentation and appears in
42 failure messages. If you don't care about the name, you can leave it out,
43 making the combined instantiation/stub declaration very terse:
c186d12 @dchelimsky move overview to Readme (where it belongs)
dchelimsky authored
44
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
45 ```ruby
f86db3e @icambron removed extra indentation in readme
icambron authored
46 double(:foo => 'bar')
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
47 ```
f26af00 @dchelimsky words
dchelimsky authored
48
0a824d2 @dchelimsky more README
dchelimsky authored
49 This is particularly nice when providing a list of test doubles to a method
50 that iterates through them:
51
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
52 ```ruby
f86db3e @icambron removed extra indentation in readme
icambron authored
53 order.calculate_total_price(stub(:price => 1.99),stub(:price => 2.99))
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
54 ```
0a824d2 @dchelimsky more README
dchelimsky authored
55
56 ## Consecutive return values
57
58 When a stub might be invoked more than once, you can provide additional
59 arguments to `and_return`. The invocations cycle through the list. The last
60 value is returned for any subsequent invocations:
61
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
62 ```ruby
f86db3e @icambron removed extra indentation in readme
icambron authored
63 die.stub(:roll).and_return(1,2,3)
64 die.roll # => 1
65 die.roll # => 2
66 die.roll # => 3
67 die.roll # => 3
68 die.roll # => 3
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
69 ```
0a824d2 @dchelimsky more README
dchelimsky authored
70
71 To return an array in a single invocation, declare an array:
72
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
73 ```ruby
f86db3e @icambron removed extra indentation in readme
icambron authored
74 team.stub(:players).and_return([stub(:name => "David")])
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
75 ```
0a824d2 @dchelimsky more README
dchelimsky authored
76
f26af00 @dchelimsky words
dchelimsky authored
77 ## Message Expectations
78
c186d12 @dchelimsky move overview to Readme (where it belongs)
dchelimsky authored
79 A message expectation is an expectation that the test double will receive a
80 message some time before the example ends. If the message is received, the
81 expectation is satisfied. If not, the example fails.
82
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
83 ```ruby
f86db3e @icambron removed extra indentation in readme
icambron authored
84 validator = double("validator")
85 validator.should_receive(:validate).with("02134")
86 zipcode = Zipcode.new("02134", validator)
87 zipcode.valid?
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
88 ```
c186d12 @dchelimsky move overview to Readme (where it belongs)
dchelimsky authored
89
90 ## Nomenclature
91
92 ### Mock Objects and Test Stubs
93
94 The names Mock Object and Test Stub suggest specialized Test Doubles. i.e.
95 a Test Stub is a Test Double that only supports method stubs, and a Mock
96 Object is a Test Double that supports message expectations and method
97 stubs.
98
99 There is a lot of overlapping nomenclature here, and there are many
100 variations of these patterns (fakes, spies, etc). Keep in mind that most of
101 the time we're talking about method-level concepts that are variations of
102 method stubs and message expectations, and we're applying to them to _one_
103 generic kind of object: a Test Double.
104
105 ### Test-Specific Extension
106
107 a.k.a. Partial Stub/Mock, a Test-Specific Extension is an extension of a
108 real object in a system that is instrumented with test-double like
109 behaviour in the context of a test. This technique is very common in Ruby
110 because we often see class objects acting as global namespaces for methods.
111 For example, in Rails:
112
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
113 ```ruby
f86db3e @icambron removed extra indentation in readme
icambron authored
114 person = double("person")
115 Person.stub(:find) { person }
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
116 ```
c186d12 @dchelimsky move overview to Readme (where it belongs)
dchelimsky authored
117
118 In this case we're instrumenting Person to return the person object we've
155cef3 @dchelimsky Make the fact that you can set message expectations on class objects
dchelimsky authored
119 defined whenever it receives the `find` message. We can also set a message
120 expectation so that the example fails if `find` is not called:
121
122 ```ruby
123 person = double("person")
124 Person.should_receive(:find) { person }
125 ```
126
127 We can do this with any object in a system because rspec-mocks adds the `stub`
128 and `should_receive` methods to every object, including class objects. When we
129 use either, RSpec replaces the method we're stubbing or mocking with its own
130 test-double-like method. At the end of the example, RSpec verifies any message
131 expectations, and then restores the original methods.
c186d12 @dchelimsky move overview to Readme (where it belongs)
dchelimsky authored
132
133 ## Expecting Arguments
134
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
135 ```ruby
f86db3e @icambron removed extra indentation in readme
icambron authored
136 double.should_receive(:msg).with(*args)
137 double.should_not_receive(:msg).with(*args)
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
138 ```
c186d12 @dchelimsky move overview to Readme (where it belongs)
dchelimsky authored
139
140 ## Argument Matchers
141
f4bd883 @dchelimsky rdoc (and a rename or two)
dchelimsky authored
142 Arguments that are passed to `with` are compared with actual arguments
c186d12 @dchelimsky move overview to Readme (where it belongs)
dchelimsky authored
143 received using ==. In cases in which you want to specify things about the
144 arguments rather than the arguments themselves, you can use any of the
145 matchers that ship with rspec-expectations. They don't all make syntactic
146 sense (they were primarily designed for use with RSpec::Expectations), but
147 you are free to create your own custom RSpec::Matchers.
148
149 rspec-mocks also adds some keyword Symbols that you can use to
150 specify certain kinds of arguments:
151
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
152 ```ruby
f86db3e @icambron removed extra indentation in readme
icambron authored
153 double.should_receive(:msg).with(no_args())
154 double.should_receive(:msg).with(any_args())
155 double.should_receive(:msg).with(1, kind_of(Numeric), "b") #2nd argument can any kind of Numeric
156 double.should_receive(:msg).with(1, boolean(), "b") #2nd argument can true or false
157 double.should_receive(:msg).with(1, /abc/, "b") #2nd argument can be any String matching the submitted Regexp
158 double.should_receive(:msg).with(1, anything(), "b") #2nd argument can be anything at all
5edfe94 @pje `:%s/ducktype/duck_type/g`
pje authored
159 double.should_receive(:msg).with(1, duck_type(:abs, :div), "b")
f86db3e @icambron removed extra indentation in readme
icambron authored
160 #2nd argument can be object that responds to #abs and #div
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
161 ```
c186d12 @dchelimsky move overview to Readme (where it belongs)
dchelimsky authored
162
163 ## Receive Counts
164
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
165 ```ruby
f86db3e @icambron removed extra indentation in readme
icambron authored
166 double.should_receive(:msg).once
167 double.should_receive(:msg).twice
168 double.should_receive(:msg).exactly(n).times
169 double.should_receive(:msg).at_least(:once)
170 double.should_receive(:msg).at_least(:twice)
171 double.should_receive(:msg).at_least(n).times
172 double.should_receive(:msg).at_most(:once)
173 double.should_receive(:msg).at_most(:twice)
174 double.should_receive(:msg).at_most(n).times
175 double.should_receive(:msg).any_number_of_times
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
176 ```
c186d12 @dchelimsky move overview to Readme (where it belongs)
dchelimsky authored
177
178 ## Ordering
179
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
180 ```ruby
f86db3e @icambron removed extra indentation in readme
icambron authored
181 double.should_receive(:msg).ordered
182 double.should_receive(:other_msg).ordered
183 #This will fail if the messages are received out of order
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
184 ```
c186d12 @dchelimsky move overview to Readme (where it belongs)
dchelimsky authored
185
40a2a0f @CoryFoy Correct spelling of "Responses"
CoryFoy authored
186 ## Setting Responses
c186d12 @dchelimsky move overview to Readme (where it belongs)
dchelimsky authored
187
188 Whether you are setting a message expectation or a method stub, you can
189 tell the object precisely how to respond. The most generic way is to pass
f4bd883 @dchelimsky rdoc (and a rename or two)
dchelimsky authored
190 a block to `stub` or `should_receive`:
c186d12 @dchelimsky move overview to Readme (where it belongs)
dchelimsky authored
191
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
192 ```ruby
f86db3e @icambron removed extra indentation in readme
icambron authored
193 double.should_receive(:msg) { value }
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
194 ```
c186d12 @dchelimsky move overview to Readme (where it belongs)
dchelimsky authored
195
f4bd883 @dchelimsky rdoc (and a rename or two)
dchelimsky authored
196 When the double receives the `msg` message, it evaluates the block and returns
c186d12 @dchelimsky move overview to Readme (where it belongs)
dchelimsky authored
197 the result.
198
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
199 ```ruby
f86db3e @icambron removed extra indentation in readme
icambron authored
200 double.should_receive(:msg).and_return(value)
201 double.should_receive(:msg).exactly(3).times.and_return(value1, value2, value3)
202 # returns value1 the first time, value2 the second, etc
203 double.should_receive(:msg).and_raise(error)
204 #error can be an instantiated object or a class
205 #if it is a class, it must be instantiable with no args
206 double.should_receive(:msg).and_throw(:msg)
207 double.should_receive(:msg).and_yield(values,to,yield)
208 double.should_receive(:msg).and_yield(values,to,yield).and_yield(some,other,values,this,time)
209 # for methods that yield to a block multiple times
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
210 ```
c186d12 @dchelimsky move overview to Readme (where it belongs)
dchelimsky authored
211
212 Any of these responses can be applied to a stub as well
213
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
214 ```ruby
f86db3e @icambron removed extra indentation in readme
icambron authored
215 double.stub(:msg).and_return(value)
216 double.stub(:msg).and_return(value1, value2, value3)
217 double.stub(:msg).and_raise(error)
218 double.stub(:msg).and_throw(:msg)
219 double.stub(:msg).and_yield(values,to,yield)
220 double.stub(:msg).and_yield(values,to,yield).and_yield(some,other,values,this,time)
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
221 ```
c186d12 @dchelimsky move overview to Readme (where it belongs)
dchelimsky authored
222
223 ## Arbitrary Handling
224
225 Once in a while you'll find that the available expectations don't solve the
226 particular problem you are trying to solve. Imagine that you expect the message
227 to come with an Array argument that has a specific length, but you don't care
228 what is in it. You could do this:
229
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
230 ```ruby
f86db3e @icambron removed extra indentation in readme
icambron authored
231 double.should_receive(:msg) do |arg|
232 arg.size.should eq(7)
233 end
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
234 ```
9d19ca0 @dchelimsky update readme
dchelimsky authored
235
c186d12 @dchelimsky move overview to Readme (where it belongs)
dchelimsky authored
236 ## Combining Expectation Details
237
238 Combining the message name with specific arguments, receive counts and responses
239 you can get quite a bit of detail in your expectations:
240
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
241 ```ruby
f86db3e @icambron removed extra indentation in readme
icambron authored
242 double.should_receive(:<<).with("illegal value").once.and_raise(ArgumentError)
a13dd64 @icambron added ruby syntax highlighting for github
icambron authored
243 ```
c186d12 @dchelimsky move overview to Readme (where it belongs)
dchelimsky authored
244
f4bd883 @dchelimsky rdoc (and a rename or two)
dchelimsky authored
245 While this is a good thing when you really need it, you probably don't really
246 need it! Take care to specify only the things that matter to the behavior of
247 your code.
248
0815a4a @nathanl Explained why `before(:all)` won't work for stubs.
nathanl authored
249 ## Use `before(:each)`, not `before(:all)`
250
251 Stubs in `before(:all)` are not supported. The reason is that all stubs and mocks get cleared out after each example, so any stub that is set in `before(:all)` would work in the first example that happens to run in that group, but not for any others.
252
253 Instead of `before(:all)`, use `before(:each)`.
254
c186d12 @dchelimsky move overview to Readme (where it belongs)
dchelimsky authored
255 ## Further Reading
256
f4bd883 @dchelimsky rdoc (and a rename or two)
dchelimsky authored
257 There are many different viewpoints about the meaning of mocks and stubs. If
258 you are interested in learning more, here is some recommended reading:
c186d12 @dchelimsky move overview to Readme (where it belongs)
dchelimsky authored
259
260 * Mock Objects: http://www.mockobjects.com/
261 * Endo-Testing: http://www.mockobjects.com/files/endotesting.pdf
262 * Mock Roles, Not Objects: http://www.mockobjects.com/files/mockrolesnotobjects.pdf
263 * Test Double Patterns: http://xunitpatterns.com/Test%20Double%20Patterns.html
264 * Mocks aren't stubs: http://www.martinfowler.com/articles/mocksArentStubs.html
265
f26af00 @dchelimsky words
dchelimsky authored
266 ## Also see
9d19ca0 @dchelimsky update readme
dchelimsky authored
267
f9f8418 @dchelimsky words
dchelimsky authored
268 * [http://github.com/rspec/rspec](http://github.com/rspec/rspec)
269 * [http://github.com/rspec/rspec-core](http://github.com/rspec/rspec-core)
270 * [http://github.com/rspec/rspec-expectations](http://github.com/rspec/rspec-expectations)
Something went wrong with that request. Please try again.