RSpec's #to Method Takes the 2nd Argument As Its failing-message

Today I learned that RSpec's #to method takes the 2nd argument as its custom failing-message.

expect(actual_value).to eq(expected_value), message

Let's say we are going to test a JSON object. The following example expects a part of the JSON object response_json['eye_colour'] to be "blue".

require 'net/https'
require 'uri'
require 'json'

RSpec.describe do
  example do
    response = Net::HTTP.get_response(URI('https://swapi.dev/api/people/1/'))
    response_json = JSON.parse(response.body)

    expect(response_json['eye_colour']).to eq('blue')
  end
end

Unfortunately, this test fails and displays messages like this:

Failures:

  1) is expected to eq "blue"
     Failure/Error: expect(response_json['eye_colour']).to eq('blue')

       expected: "blue"
            got: nil

       (compared using ==)

From this output, we could guess either response_json['eye_colour'] is set to nil or the key eye_colour is not defined. However, there is no more information here. To find out the cause of the error, an additional print-debug would be needed.

@@ -7,6 +7,7 @@ RSpec.describe do
     response = Net::HTTP.get_response(URI('https://swapi.dev/api/people/1/'))
     response_json = JSON.parse(response.body)

+    p response_json
     expect(response_json['eye_colour']).to eq('blue')
   end
 end

It's no big deal. We could do this when we need additional information. But there is another option. RSpec::Expectations::ExpectationTarget#to takes 2nd argument. When the expectation failed, RSpec shows it as a custom failing-message.

@@ -7,6 +7,6 @@ RSpec.describe do
     response = Net::HTTP.get_response(URI('https://swapi.dev/api/people/1/'))
     response_json = JSON.parse(response.body)

-    expect(response_json['eye_colour']).to eq('blue')
+    expect(response_json['eye_colour']).to eq('blue'), response_json.inspect
   end
 end

By this change, RSpec will display response_json as the custom failing-message, and then we could notice that eye_colour should be eye_color.

Failures:

  1) is expected to eq "blue"
     Failure/Error: expect(response_json['eye_colour']).to eq('blue'), response_json.inspect
       {"name"=>"Luke Skywalker", "height"=>"172", "mass"=>"77", "hair_color"=>"blond", "skin_color"=>"fair",
       "eye_color"=>"blue", "birth_year"=>"19BBY", ...}

As you can see, there are no big differences between the temporally print-debug and the custom failing-message for this example. That said, what if it shows up from time to time, what if we could reproduce it only on CI. In these cases, this kind of additional information would give you lots of insights quickly. Instead of putting print-debug code over and over again, we could always pass a custom failing-message for expectations that need more information to fix.

See Also

Gentaro "hibariya" Terada

Otaka-no-mori, Chiba, Japan
Email me

Loves Ruby, Internet, and Programming.