|
|
@@ -0,0 +1,370 @@ |
|
|
## From Lynda.com course 'RSpec Testing Framework with Ruby' |
|
|
|
|
|
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 = '[email protected]' |
|
|
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 |