Skip to content

Commit d8c613f

Browse files
committed
Flesh out more of the blog post.
1 parent afa1e1d commit d8c613f

File tree

1 file changed

+165
-1
lines changed

1 file changed

+165
-1
lines changed

source/blog/2015-05-28-rspec-3-3-has-been-released.md

Lines changed: 165 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,171 @@ and want to avoid that overhead, you can configure RSpec to
174174

175175
### Expectations: New `aggregrate_failures` API
176176

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!
178342

179343
### Mocks: Improved Failure Output
180344

0 commit comments

Comments
 (0)