Skip to content

Instantly share code, notes, and snippets.

@dukeimg
Created February 28, 2016 09:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save dukeimg/309b794abf5dc26dae49 to your computer and use it in GitHub Desktop.
Save dukeimg/309b794abf5dc26dae49 to your computer and use it in GitHub Desktop.
RSpec -- Expectation Matchers
describe 'Expectation Matchers' do
describe 'equivalence matchers' do
it 'will match loose equality with #eq' do
a = "2 cats"
b = "2 cats"
expect(a).to eq(b)
expect(a).to be == b # synonym for #eq
c = 17
d = 17.0
expect(c).to eq(d) # different types, but "close enough"
end
it 'will match value equality with #eql' do
a = "2 cats"
b = "2 cats"
expect(a).to eql(b) # just a little stricter
c = 17
d = 17.0
expect(c).not_to eql(d) # not the same, close doesn't count
end
it 'will match identity equality with #equal' do
a = "2 cats"
b = "2 cats"
expect(a).not_to equal(b) # same value, but different object
c = b
expect(b).to equal(c) # same object
expect(b).to be(c) # synonym for #equal
end
end
describe 'truthiness matchers' do
it 'will match true/false' do
expect(1 < 2).to be(true) # do not use 'be_true'
expect(1 > 2).to be(false) # do not use 'be_false'
expect('foo').not_to be(true) # the string is not exactly true
expect(nil).not_to be(false) # nil is not exactly false
expect(0).not_to be(false) # 0 is not exactly false
end
it 'will match truthy/falsey' do
expect(1 < 2).to be_truthy
expect(1 > 2).to be_falsey
expect('foo').to be_truthy # any value counts as true
expect(nil).to be_falsey # nil counts as false
expect(0).not_to be_falsey # but 0 is still not falsey enough
end
it 'will match nil' do
expect(nil).to be_nil
expect(nil).to be(nil) # either way works
expect(false).not_to be_nil # nil only, just like #nil?
expect(0).not_to be_nil # nil only, just like #nil?
end
end
describe 'numeric comparison matchers' do
it 'will match less than/greater than' do
expect(10).to be > 9
expect(10).to be >= 10
expect(10).to be <= 10
expect(9).to be < 10
end
it 'will match numeric ranges' do
expect(10).to be_between(5, 10).inclusive
expect(10).not_to be_between(5, 10).exclusive
expect(10).to be_within(1).of(11)
expect(5..10).to cover(9)
end
end
describe 'collection matchers' do
it 'will match arrays' do
array = [1,2,3]
expect(array).to include(3)
expect(array).to include(1,3)
expect(array).to start_with(1)
expect(array).to end_with(3)
expect(array).to match_array([3,2,1])
expect(array).not_to match_array([1,2])
expect(array).to contain_exactly(3,2,1) # similar to match_array
expect(array).not_to contain_exactly(1,2) # but use individual args
end
it 'will match strings' do
string = 'some string'
expect(string).to include('ring')
expect(string).to include('so', 'ring')
expect(string).to start_with('so')
expect(string).to end_with('ring')
end
it 'will match hashes' do
hash = {:a => 1, :b => 2, :c => 3}
expect(hash).to include(:a)
expect(hash).to include(:a => 1)
expect(hash).to include(:a => 1, :c => 3)
expect(hash).to include({:a => 1, :c => 3})
expect(hash).not_to include({'a' => 1, 'c' => 3})
end
end
describe 'other useful matchers' do
it 'will match strings with a regex' do
# This matcher is a good way to "spot check" strings
string = 'The order has been received.'
expect(string).to match(/order(.+)received/)
expect('123').to match(/\d{3}/)
expect(123).not_to match(/\d{3}/) # only works with strings
email = 'someone@somewhere.com'
expect(email).to match(/\A\w+@\w+\.\w{3}\Z/)
end
it 'will match object types' do
expect('test').to be_instance_of(String)
expect('test').to be_an_instance_of(String) # alias of #be_instance_of
expect('test').to be_kind_of(String)
expect('test').to be_a_kind_of(String) # alias of #be_kind_of
expect('test').to be_a(String) # alias of #be_kind_of
expect([1,2,3]).to be_an(Array) # alias of #be_kind_of
end
it 'will match objects with #respond_to' do
string = 'test'
expect(string).to respond_to(:length)
expect(string).not_to respond_to(:sort)
end
it 'will match class instances with #have_attributes' do
class Car
attr_accessor :make, :year, :color
end
car = Car.new
car.make = 'Dodge'
car.year = 2010
car.color = 'green'
expect(car).to have_attributes(:color => 'green')
expect(car).to have_attributes(
:make => 'Dodge', :year => 2010, :color => 'green'
)
end
it 'will match anything with #satisfy' do
# This is the most flexible matcher
expect(10).to satisfy do |value|
(value >= 5) && (value <=10) && (value % 2 == 0)
end
end
end
describe 'predicate matchers' do
it 'will match be_* to custom methods ending in ?' do
# drops "be_", adds "?" to end, calls method on object
# Can use these when methods end in "?", require no arguments,
# and return true/false.
# with built-in methods
expect([]).to be_empty # [].empty?
expect(1).to be_integer # 1.integer?
expect(0).to be_zero # 0.zero?
expect(1).to be_nonzero # 1.nonzero?
expect(1).to be_odd # 1.odd?
expect(2).to be_even # 1.even?
# be_nil is actually an example of this too
# with custom methods
class Product
def visible?; true; end
end
product = Product.new
expect(product).to be_visible # product.visible?
expect(product.visible?).to be true # exactly the same as this
end
it 'will match have_* to custom methods like has_*?' do
# changes "have_" to "has_", adds "?" to end, calls method on object
# Can use these when methods start with "has_", end in "?",
# and return true/false. Can have arguments, but not required.
# with built-in methods
hash = {:a => 1, :b => 2}
expect(hash).to have_key(:a) # hash.has_key?
expect(hash).to have_value(2) # hash.has_value?
# with custom methods
class Customer
def has_pending_order?; true; end
end
customer = Customer.new
expect(customer).to have_pending_order # customer.has_pending_order?
expect(customer.has_pending_order?).to be true # same as this
end
end
describe 'observation matchers' do
# Note that all of these use "expect {}", not "expect()".
# It is a special block format that allows a
# process to take place inside of the expectation.
it 'will match when events change object attributes' do
# calls the test before the block,
# then again after the block
array = []
expect { array << 1 }.to change(array, :empty?).from(true).to(false)
class WebsiteHits
attr_accessor :count
def initialize; @count = 0; end
def increment; @count += 1; end
end
hits = WebsiteHits.new
expect { hits.increment }.to change(hits, :count).from(0).to(1)
end
it 'will match when events change any values' do
# calls the test before the block,
# then again after the block
# notice the "{}" after "change",
# can be used on simple variables
x = 10
expect { x += 1 }.to change {x}.from(10).to(11)
expect { x += 1 }.to change {x}.by(1)
expect { x += 1 }.to change {x}.by_at_least(1)
expect { x += 1 }.to change {x}.by_at_most(1)
# notice the "{}" after "change",
# can contain any block of code
z = 11
expect { z += 1 }.to change { z % 3 }.from(2).to(0)
# Must have a value before the block
# Must change the value inside the block
end
it 'will match when errors are raised' do
# observes any errors raised by the block
expect { raise StandardError }.to raise_error
expect { raise StandardError }.to raise_exception
expect { 1 / 0 }.to raise_error(ZeroDivisionError)
expect { 1 / 0 }.to raise_error.with_message("divided by 0")
expect { 1 / 0 }.to raise_error.with_message(/divided/)
# Note that the negative form does
# not accept arguments
expect { 1 / 1 }.not_to raise_error
end
it 'will match when output is generated' do
# observes output sent to $stdout or $stderr
expect { print('hello') }.to output.to_stdout
expect { print('hello') }.to output('hello').to_stdout
expect { print('hello') }.to output(/ll/).to_stdout
expect { warn('problem') }.to output(/problem/).to_stderr
end
end
describe 'compound expectations' do
it 'will match using: and, or, &, |' do
expect([1,2,3,4]).to start_with(1).and end_with(4)
expect([1,2,3,4]).to start_with(1) & include(2)
expect(10 * 10).to be_odd.or be > 50
array = ['hello', 'goodbye'].shuffle
expect(array.first).to eq("hello") | eq("goodbye")
end
end
describe 'composing matchers' do
# some matchers accept matchers as arguments. (new in rspec3)
it 'will match all collection elements using a matcher' do
array = [1,2,3]
expect(array).to all( be < 5 )
end
it 'will match by sending matchers as arguments to matchers' do
string = "hello"
expect { string = "goodbye" }.to change { string }.
from( match(/ll/) ).to( match(/oo/) )
hash = {:a => 1, :b => 2, :c => 3}
expect(hash).to include(:a => be_odd, :b => be_even, :c => be_odd)
expect(hash).to include(:a => be > 0, :b => be_within(2).of(4))
end
it 'will match using noun-phrase aliases for matchers' do
# These are built-in aliases that make
# specs read better by using noun-based
# phrases instead of verb-based phrases.
# valid but awkward example
fruits = ['apple', 'banana', 'cherry']
expect(fruits).to start_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).to start_with( a_string_starting_with('a') ) &
include( a_string_matching(/a.a.a/) ) &
end_with( a_string_ending_with('y') )
# valid but awkward example
array = [1,2,3,4]
expect(array).to start_with( be <= 2 ) |
end_with( be_within(1).of(5) )
# improved version of the previous example
# "be <= 2" becomes "a_value <= 2"
# "be_within" becomes "a_value_within"
array = [1,2,3,4]
expect(array).to start_with( a_value <= 2 ) |
end_with( a_value_within(1).of(5) )
end
end
end
@elissonmichael
Copy link

Thanks for sharing this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment