3. Aliases
RSpec 3 provides one or more aliases for all the built-in matchers.
consistent phrasing ("a_[type of object][verb]ing")
so they are easy to guess:
a_string_starting_withfor start_with
a_string_includinga_collection_includinga_hash_includingaliases
of include
see a list of them in this gist
easier to read when used in compound expresions or composed
matchers
and also more readable failure mssages.
RSpec 3 made it easy to define an alias for some built-in matcher or even your
custom matchers. Here is the bit of code to define the a_string_starting_with
alias of start_with:
RSpec::Matchers.alias_matcher:a_string_starting_with,:start_with
3 / 9
4. What are these composable matchers good for?
They will save you from this ...
describe"GET/api/areas/:area_id/pscs"do
context"whengivenvaliddata"do
it"returnsthePSCSforgivenareainJSON"do
get"/api/areas/#{area.id}/pscs",
{access_token:access_token_for(user),level_id:area.default_level.id},
{'Accept'=>Mime::JSON}
expect(response.status).tobe(200)
expect(response.content_type).tobe(Mime::JSON)
json_response=json(response.body)
expect(json_response[:latitude]).to eq(area.location.point.latitude.to_f)
expect(json_response[:longitude]).to eq(area.location.point.longitude.to_f)
#otherlongexpectshere
expect(level_node[:previous_level][:level_id]).toeq(area.parkings_levels.order_by_lev
expect(level_node[:image][:url]).to eq(area.level_image(area.default_leve
pscs_latitudes=json_response[:pscs].map{|e|e[:pscs][:latitude]}
expect(pscs_latitudes).toinclude(area.pscs_on_level(area.default_level.id).first.poin
end
end
end
4 / 9
5. The solution
is to use the matchmatcher, which became in rspec 3 a kind of black hole for any rspec
matcher.
describe"GET/api/areas/:area_id/pscs"do
context"whengivenvaliddata"do
it"returnsthePSCSforgivenareainJSON"do
get"/api/areas/#{area.id}/pscs",
{level_id:area.default_level.id},
{
'Authorization'=>"Bearer#{access_token_for(user)}",
'Accept'=>Mime::JSON
}
expect(response).tohave_status(200).and_content_type(Mime::JSON)
json_response=json(response.body)
expect(json_response).tomatch(pscs_list_composed_matcher(area:area))
expect(json_response[:pscs]).tocontain_latitude(area.pscs_on_level(area.default_level
end
end
end
5 / 9
6. The object passed to matchis more like a big hash containing any rspec
matchers as values for his keys:
modulePscsHelpers
defpscs_list_composed_matcher(area:,current_level:nil,is_favorite:false)
current_level=area.default_level
{
latitude:area.location.point.latitude.to_f,
longitude:area.location.point.longitude.to_f,
is_favorite:is_favorite,
zoomLevel:(a_value>0),
level:current_level_matcher(area,current_level),
pscs:an_instance_of(Array)
}
end
defcurrent_level_matcher(area,current_level)
{
level_id:current_level.id,
name:current_level.name,
default_level:level_matcher(area.default_level),
next_level: level_matcher(area.levels.first),
previous_level:level_matcher(area.levels.last),
image:level_image_matcher(area,current_level)
}
end
#reusable
deflevel_matcher(level)
#...
end
deflevel_image_matcher(area,current_level)
#... 6 / 9
7. 2. Custom matchers
2.1 How to:
RSpec::Matchers.define:contain_latitudedo|expected|
latitudes=[]
matchdo|actual|
latitudes=actual.collect{|item|item[:pscs][:latitude]}
latitudes.find{|lat|lat.to_s==expected.to_s}
end
failure_messagedo|actual|
"expectedthatpscs_listwithlatitudesn #{latitudes}nwouldcontainthe'#{expec
end
end
#anduseitlikethis:
expect(json_response[:pscs]).tocontain_latitude(45.4545)
#orusingacompoundexpression
expect(json_response[:pscs])
.tocontain_latitude(45.4545)
.andcontain_longitude(25.90)
7 / 9
8. 2.2 Chained matchers with fluent interface
When you want something more expressive then .andor .orfrom previous
example
modulePscsHelpers
#scopedmatcherswiththePscsHelpersmodule
extendRSpec::Matchers::DSL
matcher:contain_a_latitude_bigger_thando|first|
latitudes=[]
matchdo|actual|
latitudes=actual.collect{|item|item[:pscs][:latitude]}
bigger=latitudes.find{|lat|lat>expected}
smaller=latitudes.find{|lat|lat<second}
bigger&&smaller
end
chain:but_smaller_thando|second|
@second=second
end
end
end
#andthefancyexpectationusingit
expect(response).tocontain_a_latitude_bigger_than(43).but_smaller_than(47)
8 / 9
9. Resources
RSpec 3 - Composable Matchers
List of RSpec 3 Aliases gist
Define Matcher
9 / 9