@@ -174,7 +174,171 @@ and want to avoid that overhead, you can configure RSpec to
174
174
175
175
### Expectations: New ` aggregrate_failures ` API
176
176
177
- ### Expectation: Improved Failure Output
177
+ When you've got multiple independent expectations to make about a
178
+ particular result, you've generally got two routes you can take. One way is
179
+ to define a separate example for each expectation:
180
+
181
+ ~~~ ruby
182
+ RSpec .describe Client do
183
+ let(:response ) { Client .make_request }
184
+
185
+ it " returns a 200 response" do
186
+ expect(response.status).to eq(200 )
187
+ end
188
+
189
+ it " indicates the response body is JSON" do
190
+ expect(response.headers).to include (" Content-Type" => " application/json" )
191
+ end
192
+
193
+ it " returns a success message" do
194
+ expect(response.body).to eq(' {"message":"Success"}' )
195
+ end
196
+ end
197
+ ~~~
198
+
199
+ This follows the "one expectation per example" guideline that encourages
200
+ you to keep each spec focused on a single facet of behavior. Alternately,
201
+ you can put all the expectations in a single example:
202
+
203
+ ~~~ ruby
204
+ RSpec .describe Client do
205
+ it " returns a successful JSON response" do
206
+ response = Client .make_request
207
+
208
+ expect(response.status).to eq(200 )
209
+ expect(response.headers).to include (" Content-Type" => " application/json" )
210
+ expect(response.body).to eq(' {"message":"Success"}' )
211
+ end
212
+ end
213
+ ~~~
214
+
215
+ This latter approach is going to be faster, as the request is only made once
216
+ rather than three times. However, if the status code is not a 200, the example
217
+ will abort on the first expectation and you won't be able to see whether or not
218
+ the latter two expectations passed or not.
219
+
220
+ RSpec 3.3 has a new feature that helps when, for speed or other reasons, you
221
+ want to put multiple expectations in a single example. Simply wrap your
222
+ expectations in an ` aggregate_failures ` block:
223
+
224
+ ~~~ ruby
225
+ RSpec .describe Client do
226
+ it " returns a successful JSON response" do
227
+ response = Client .make_request
228
+
229
+ aggregate_failures " testing response" do
230
+ expect(response.status).to eq(200 )
231
+ expect(response.headers).to include (" Content-Type" => " application/json" )
232
+ expect(response.body).to eq(' {"message":"Success"}' )
233
+ end
234
+ end
235
+ end
236
+ ~~~
237
+
238
+ Within the ` aggregate_failures ` block, expectations do not cause the
239
+ example to abort. Instead, a single _ aggregate_ exception will be
240
+ raised at the end containing multiple sub-failures which RSpec will
241
+ format nicely for you:
242
+
243
+ ~~~
244
+ 1) Client returns a successful response
245
+ Got 3 failures from failure aggregation block "testing reponse".
246
+ # ./spec/client_spec.rb:5
247
+
248
+ 1.1) Failure/Error: expect(response.status).to eq(200)
249
+
250
+ expected: 200
251
+ got: 404
252
+
253
+ (compared using ==)
254
+ # ./spec/client_spec.rb:6
255
+
256
+ 1.2) Failure/Error: expect(response.headers).to include("Content-Type" => "application/json")
257
+ expected {"Content-Type" => "text/plain"} to include {"Content-Type" => "application/json"}
258
+ Diff:
259
+ @@ -1,2 +1,2 @@
260
+ -[{"Content-Type"=>"application/json"}]
261
+ +"Content-Type" => "text/plain",
262
+ # ./spec/client_spec.rb:7
263
+
264
+ 1.3) Failure/Error: expect(response.body).to eq('{"message":"Success"}')
265
+
266
+ expected: "{\"message\":\"Success\"}"
267
+ got: "Not Found"
268
+
269
+ (compared using ==)
270
+ # ./spec/client_spec.rb:8
271
+ ~~~
272
+
273
+ ` RSpec::Core ` provides improved support for this feature through the use of
274
+ metadata. Instead of wrapping the expectations with ` aggregate_failures ` ,
275
+ simply tag the example or group with ` :aggregate_failures ` :
276
+
277
+ ~~~ ruby
278
+ RSpec .describe Client , :aggregate_failures do
279
+ it " returns a successful JSON response" do
280
+ response = Client .make_request
281
+
282
+ expect(response.status).to eq(200 )
283
+ expect(response.headers).to include (" Content-Type" => " application/json" )
284
+ expect(response.body).to eq(' {"message":"Success"}' )
285
+ end
286
+ end
287
+ ~~~
288
+
289
+ If you want to enable this feature everywhere, you can use [ ` define_derived_metadata ` ] ( TODO ) :
290
+
291
+ ~~~ ruby
292
+ RSpec .configure do |c |
293
+ c.define_derived_metadata do |meta |
294
+ meta[:aggregate_failures ] = true
295
+ end
296
+ end
297
+ ~~~
298
+
299
+ Of course, you may not want this enabled everywhere. When you've got _ dependent_ expectations
300
+ (e.g. where an expectation only makes sense if the prior expectation passed), or if you're
301
+ using expectations to express a pre-condition, you'll probably want the example to immediately abort
302
+ when the expectation fails.
303
+
304
+ ### Expectations: Improved Failure Output
305
+
306
+ RSpec 3.3 includes improved failure messages across the board for all matchers.
307
+ Test doubles now look prettier in our failure messages:
308
+
309
+ ~~~
310
+ Failure/Error: expect([foo]).to include(bar)
311
+ expected [#<Double "Foo">] to include #<Double "Bar">
312
+ Diff:
313
+ @@ -1,2 +1,2 @@
314
+ -[#<Double "Bar">]
315
+ +[#<Double "Foo">]
316
+ ~~~
317
+
318
+ In addition, RSpec's improved formatting for ` Time ` and other objects will
319
+ now be used wherever those objects are inspected for every matcher. So, for
320
+ example, where you used to get this:
321
+
322
+ ~~~
323
+ Failure/Error: expect([Time.now]).to include(Time.now)
324
+ expected [2015-06-09 07:48:06 -0700] to include 2015-06-09 07:48:06 -0700
325
+ ~~~
326
+
327
+ ...you'll now get this instead:
328
+
329
+ ~~~
330
+ Failure/Error: expect([Time.now]).to include(Time.now)
331
+ expected [2015-06-09 07:49:16.610635000 -0700] to include 2015-06-09 07:49:16.610644000 -0700
332
+ Diff:
333
+ @@ -1,2 +1,2 @@
334
+ -[2015-06-09 07:49:16.610644000 -0700]
335
+ +[2015-06-09 07:49:16.610635000 -0700]
336
+ ~~~
337
+
338
+ ...which makes it much clearer that the time objects differ at the level of milliseconds.
339
+
340
+ Thanks to Gavin Miller, Nicholas Chmielewski and Siva Gollapalli for contributing to
341
+ these improvements!
178
342
179
343
### Mocks: Improved Failure Output
180
344
0 commit comments