describe'equivalence matchers'doit'will match loose equality with #eq'doa='2 cats'b='2 cats'expect(a).toeq(b)expect(a).tobe == b# synonym for #eqc=17d=17.0expect(c).toeq(d)# dofferent types, but close enoughendit'will match value equality with #eql'doa='2 cats'b='2 cats'expect(a).toeql(b)# just a litter stricterc=17d=17.0expect(c).not_toeql(d)# not the same, close doesn't countendit'will match identity equality with #equal'doa='2 cats'b='2 cats'expect(a).not_toequal(b)# same value, but different objectc=bexpect(b).toequal(c)# same objectexpect(b).tobe(c)# synonym for #equalendend
Truthiness matchers
describe'truthiness matchers'doit'will match true/false'doexpect(1 < 2).tobe(true)# do not use 'be_true'expect(1 > 2).tobe(false)# do not use 'be_false'expect('foo').not_tobe(true)# this string is not exactly trueexpect(nil).not_tobe(false)# nils is not exactly falseexpect(0).not_tobe(false)# 0 is not exactly falseendit'will match truthy/falsey'doexpect(1 < 2).tobe_truthyexpect(1 > 2).tobe_falseyexpect('foo').tobe_truthy# any value counts as trueexpect(nil).tobe_falsey# nil counts as falseexpect(0).not_tobe_falsey# but 0 is still not falsey enoughendit'will match nil'doexpect(nil).tobe_nilexpect(nil).tobe(nil)# either way worksexpect(false).not_tobe_nil# nil only, just like #nil?expect(0).not_tobe_nil# nil only, just like #nil?endend
Numeric comparison matchers
describe'numeric comparison matchers'doit'will match less than/grater than'doexpect(10).tobe > 9expect(10).tobe >= 10expect(10).tobe <= 10expect(9).tobe < 10endit'will match numeric ranges'doexpect(10).tobe_between(5,10).inclusiveexpect(10).not_tobe_between(5,10).exclusiveexpect(10).tobe_within(1).of(11)expect(5..10).tocover(9)endend
Collection matchers
describe'collection matchers'doit'will match arrays'doarray=[1,2,3]expect(array).toinclude(3)expect(array).toinclude(1,3)expect(array).tostart_with(1)expect(array).toend_with(3)expect(array).tomatch_array([3,2,1])# even in different orderexpect(array).not_tomatch_array([1,2])# but not if an element is missingexpect(array).tocontain_exactly(3,2,1)# similar to match_arrayexpect(array).not_tocontain_exactly(1,2)# but use individual argsendit'will match strings'dostring='some string'expect(string).toinclude('ring')expect(string).toinclude('so','ring')expect(string).tostart_with('so')expect(string).toend_with('ring')endit'will match hashes'dohash={a: 1,b: 2,c: 3}expect(hash).toinclude(:a)expect(hash).toinclude(a: 1)expect(hash).toinclude(a: 1,c: 3)expect(hash).toinclude({a: 1,c:3})expect(hash).not_toinclude({'a'=>1,'c'=>3})endend
Other useful matchers
describe'other useful matchers'doit'will match strings with a regex'do# this matchers is a good way to 'spot check' stringsstring='The order has been received.'expect(string).tomatch(/order(.+)received/)expect('123').tomatch(/\d{3}/)expect(123).not_tomatch(/\d{3}/)# only works with stringsemail='the@minijohn.me'expect(email).tomatch(/\A\w+@\w+\.\w{2}\Z/)endit'will match object types'doexpect('test').tobe_instance_of(String)expect('test').tobe_a_kind_of(String)# alias of #be_kind_ofexpect('test').tobe_a(String)# alias of #be_kind_ofexpect([1,2,3]).tobe_an(Array)# alias of #be_kind_ofendit'will match objects with #respond_to'dostring='test'expect(string).torespond_to(:length)expect(string).not_torespond_to(:sort)endit'will match class instances with #have_attributes'doclassTheMiniJohnattr_accessor:name,:age,:superpowerendtmj=TheMiniJohn.newtmj.name='John'tmj.age='23'tmj.superpower='50% Bulletproof'expect(tmj).tohave_attributes(name: 'John')expect(tmj).tohave_attributes(name: 'John',age: '23',superpower: '50% Bulletproof')endit'will match anything with #satisfy'do# This is the most flexible matcherexpect(10).tosatisfydo |x|
(x >= 5) && (x <= 10) && (x % 2 == 0)endendend
Predicate matchers
describe'predicate matchers'doit'will match be_* to custom methods ending in ?'do# drops "be_", adds "?" to end, calls metohd on object# can use these when methods end in "?", require no arguments# returns true/false# with build-in methodsexpect([]).tobe_empty# [].empty?expect(1).tobe_integer# 1.integer?expect(0).tobe_zero# 0.zero?expect(1).tobe_nonzero# 1.nonzero?expect(1).tobe_odd# 1.odd?expect(2).tobe_even# 2.even?# be_nil is actually an example of this too# with custom methodsclassProductdefvisible?;true;endendproduct=Product.newexpect(product).tobe_visible# product.visible?expect(product.visible?).tobetrue# exactly the same as thisendit'will match have_* to custom method like has_*?'do# changes "have_" to "has_", adds "?" to end, calls method on object# can use these when methods starts with "has_", end in "?"# returns true/false. Can have arguments, but not required# with build-in methodshash={a:1,b:2}expect(hash).tohave_key(:a)# hash.has_key?expect(hash).tohave_value(2)# hash.has_value?# with custom methodsclassCustomerdefhas_pending_order?;true;endendcustomer=Customer.newexpect(customer).tohave_pending_order# customer.has_pending_order?expect(customer.has_pending_order?).tobetrue# exactly same as thisendend
Observation matchers
describe'observation matchers'do# Note that all these use "expect {}", not "expect()".# It is a special block format that allows a process# to take place inside of the expectationit'will match when events change object attributes'do# calls the test before and after the blockarray=[]expect{array << 1}.tochange(array,:empty?).from(true).to(false)classViewsCountattr_accessor:countdefinitialize;@count=0;enddefincrement;@count += 1;endendviews=ViewsCount.newexpect{views.increment}.tochange(views,:count).from(0).to(1)endit'will match when events change any values'do# calls the test before and after the block# notice the "{}" after "change" can be used on simple varsx=10expect{x += 1}.tochange{x}.from(10).to(11)expect{x += 1}.tochange{x}.by(1)expect{x += 1}.tochange{x}.by_at_least(1)expect{x += 1}.tochange{x}.by_at_most(1)# notice the "{}" after "change" can containe any blockz=11expect{z += 1}.tochange{z % 3}.from(2).to(0)# must have a value before the block and change inside it.endit'will match wehn errors are raised'do# observes any errors raised by the blockexpect{raiseStandardError}.toraise_errorexpect{raiseStandardError}.toraise_exceptionexpect{1 / 0}.toraise_error(ZeroDivisionError)expect{1 / 0}.toraise_error.with_message("divided by 0")expect{1 / 0}.toraise_error.with_message(/divided/)# Note that the negative form dopes not accept argumentsexpect{1 / 1}.not_toraise_errorendit'will match when output is generated'do# observes output send to $stdout or $stderrexpect{print('hello')}.tooutput.to_stdoutexpect{print('hello')}.tooutput('hello').to_stdoutexpect{print('hello')}.tooutput(/ll/).to_stdoutexpect{warn('problem')}.tooutput(/problem/).to_stderrendend
describe'composing matchers'do# some matchers accept matchers as arguments (new in RSpec 3)it'will match all collection elements using a matcher'doarray=[1,2,3]expect(array).toall(be < 5)endit'will match by sending matchers as arguments to matchers'dostring='hello'expect{string='goodbye'}.tochange{string}.from(match(/ll/)).to(match(/oo/))hash={a: 1,b: 2,c: 3}expect(hash).toinclude(a: be_odd,b: be_even,c: be_odd)expect(hash).toinclude(a: be > 0,b: be_within(2).of(4))endit'will match using noun-phrase aliases for matchers'do# there are built-in aliases that make specs read better# by using noun-based phrases instead of verb-based.# 1.) valid but awkward examplefruits=['apple','banana','cherry']expect(fruits).tostart_with(start_with('a')) &
include(match(/a.a.a/)) & end_with(end_with('y'))# improved version of the previous example# "start_with" becomes "a_string_starting_with"# "end_with" becomes "a_string_ending_with"# "match" becomes "a_string_matching"fruits=['apple','banana','cherry']expect(fruits).tostart_with(a_string_starting_with('a')) &
include(a_string_matching(/a.a.a/)) &
end_with(a_string_ending_with('y'))# 2.) valid but awkward example againarray=[1,2,3,4]expect(array).tostart_with(be <= 2) | end_with(be_within(1).of(5))# improved version of the previous example# "be <= 2" becomes "a_value <= 2"# "be_withing" becomes "a_value_within"array=[1,2,3,4]expect(array).tostart_with(a_value <= 2) | end_with(a_value_within(1).of(5))endend