Examples are decalred using the it method
Assertions are called Expectations
should and should_not are called Modifiers
Matchers are the operators in the assertions (==, >, be_true, etc.)
$ gem install rspec
...
Successfully installed rspec-core
Successfully installed rspec-expectations
Successfully installed rspec-mocks
Successfully installed rspec
4 gems installed
$ rspec --init
create spec/spec_helper.rb
create .rspec
group :development, :test do
gem 'rspec-rails'
end
$ bundle install
...
Installing rspec-core
Installing rspec-expectations
Installing rspec-mocks
Installing rspec
Installing rspec-rails
$ rails generate rspec:install
create .rspec
create spec/spec_helper.rb
spec/spec_helper.rb
# requires all helper files within spec/support
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
...
RSpec.configure do |config|
# change the default mocking framework
config.mock_with :mocha
...
end
running all the '_spec.rb' files within /spec
$ rspec
running a specific directory
$ rspec spec/models
running a specific test
$ rspec spec/models/zombie_spec.rb
running a specific line
$ rspec spec/models/zombie_spec.rb:4
Mark example as pending
it "is named Ash"
xit "is named Ash" do
...
end
it "is named Ash" do
pending
...
end
# basic
zombie.name.should == 'Ash'
zombie.alive.should be_false
zombie.alive.should be_true

zombie.height.should > 5
zombie.brains.should be < 1
zombie.height.should >= 5
zombie.height.should < 5
zombie.height.should_not == 5
# predicate
zombie.should
class Zombie
def hungry?
true
end
end
zombie.hungry?.should == true
zombie.hungry?.should be_true
zombie.should be_hungry
# match
zombie.name.should match(/Ash Clone \d/)
# include
zombie.tweets.should include(tweet1)
# have
# rather than zombie.weapons.count.should == 2
zombie.should have(2).weapons
zombie.should have_at_least(2).weapons
zombie.should have_at_most(2).weapons
# change
expect { zombie.save }.to change { Zombie.count }.by(1)
expect { zombie.save }.to change { Zombie.count }.from(1).to(5)
# raise_error
expect { zombie.save! }.to raise_error(
ActiveRecord::RecordInvalid
)
# to
# not_to
# to_not
# more matchers
@zombie.should respond_to(:hungry?)
@width.should be_within(0.1).of(33.3)
@zombie.should exist
@zombie.should satisfy { |zombie| zombie.hungry? }
@hungry_zombie.should be_kind_of(Zombie)
@status.should be_an_instance_of(String)
Implicit Subject
#describe Zombie do
# it 'responds to name' do
# zombie = Zombie.new
# zombie.should respond_to(:name)
# end
# end
# only works using describe with a class
describe Zombie do
it 'responds to name' do
# subject = Zombie.new
subject.should respond_to(:name)
end
end
Implicit Receiver
# describe Zombie do
# it 'responds to name' do
# subject.should respond_to(:name)
# end
# end
describe Zombie do
it 'responds to name' do
should respond_to(:name)
end
end
it without name
# it 'responds to name' { should respond_to(:name) }
it { should respond_to(:name) }
its
# it { subject.name.should == 'Ash' }
its(:name) { should == 'Ash' }
# more examples
its(:name) { should == 'Ash' }
its(:weapons) { should include(weapon) }
its(:brain) { should be_nil }
its('tweets.size') { should == 2 }
Nesting examples
# duplication!!
describe Zombie do
it 'craves brains when hungry'
it 'with a veggie preference still craves brains when hungry'
it 'with a veggie preference prefers vegan brains when hungry'
end
# duplication!
describe Zombie do
it 'craves brains when hungry'
describe 'with a veggie preference' do
it 'still craves brains when hungry'
it 'prefers vegan brains when hungry'
end
end
# better
describe Zombie do
describe 'when hungry' do
it 'craves brains'
describe 'with a veggie preference' do
it 'still craves brains'
it 'prefers vegan brains'
end
end
end
# even better with context instead of describe
describe Zombie do
context 'when hungry' do
it 'craves brains'
context 'with a veggie preference' do
it 'still craves brains'
it 'prefers vegan brains'
end
end
end
Using Subject
# subject without name
subject { Zombie.new(vegetarian: true, weapons: [axe]) }
its(:weapons) { should include(axe) }
# naming the subject
let(:zombie) { Zombie.new(vegetarian: true, weapons: [axe]) }
let(:axe) { Weapon.new(name: 'axe') }
subject { zombie }
# new subject syntax
subject(:zombie) { Zombie.new(vegetarian: true, weapons: [axe]) })
let(:axe) { Weapon.new(name: 'axe') }
# make sure let executes everytime (by default it's lazy evaluation)
let!(:zombie) { Zombie.create }
Refactor specs using it/its/subject
before
describe Zombie do
it 'has no name' do
@zombie = Zombie.create
@zombie.name.should be_nil?
end
it 'craves brains' do
@zombie = Zombie.create
@zombie.should be_craving_brains
end
it 'should not be hungry after eating brains' do
@zombie = Zombie.create
@zombie.hungry.should be_true
@zombie.eat(:brains)
@zombie.hungry.should be_false
end
end
after
describe Zombie do
let(:zombie) { Zombie.create }
subject { zombie }
its(:name) { should be_nil? }
it { should be_craving_brains }
it 'should not be hungry after eating brains' do
expect { zombie.eat(:brains) }.to change {
zombie.hungry
}.from(true).to(false)
end
end
hooks
# run before each example
before(:each)
# run once before all
before(:all)
# run after each
after(:each)
# run after all
after(:all)
Stub is for replacing a method with code that returns a specific result. Mock is a stub with an expectation that the method gets called.
# stubbing
zombie.weapon.stub(:slice)
Zoogle.stub(:graveyard_locator).with(zombie.graveyard)
.and_return({latitude: 2, longitude: 3})
# stubbing object
loc = stub(latitude: 2, longitude: 3) # hash to object with methods
Zoogle.stub(:graveyard_locator).returns(loc)
# mocking
zombie.weapon.should_receive(:slice)
Zoogle.should_receive(:graveyard_locator).with(zombie.graveyard)
Zoogle.should_receive(:graveyard_locator).with(zombie.graveyard)
.and_return({latitude: 2, longitude: 3})
# more options
target.should_receive(:function).once
.twice
.exactly(3).times
.at_least(2).times
.at_most(3).times
.any_number_of_times

target.should_receive(:function).with(no_args())
.with(any_args())
.with("B", anything())
.with(3, kind_of(Numeric))
.with(/zombie ash/)