## Introduction ### Vocabulary ***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.) ### Install RSpec ```bash $ gem install rspec ... Successfully installed rspec-core Successfully installed rspec-expectations Successfully installed rspec-mocks Successfully installed rspec 4 gems installed ``` ### Initialize RSpec in your project directory ```bash $ rspec --init create spec/spec_helper.rb create .rspec ``` ### Initialize RSpec in Rails ```ruby group :development, :test do gem 'rspec-rails' end ``` ```bash $ 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 ``` ### Configuration in Rails spec/spec_helper.rb ```ruby # 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 specs running all the '_spec.rb' files within /spec ```bash $ rspec ``` running a specific directory ```bash $ rspec spec/models ``` running a specific test ```bash $ rspec spec/models/zombie_spec.rb ``` running a specific line ```bash $ rspec spec/models/zombie_spec.rb:4 ``` Mark example as pending ```ruby it "is named Ash" xit "is named Ash" do ... end it "is named Ash" do pending ... end ``` ## Matchers ```ruby # 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) ``` ## DRY Spec Implicit Subject ```ruby #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 ```ruby # 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 ```ruby # it 'responds to name' { should respond_to(:name) } it { should respond_to(:name) } ``` its ```ruby # 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 ```ruby # 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 ```ruby # 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* ```ruby 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* ```ruby 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 & Tags hooks ```ruby # run before each example before(:each) # run once before all before(:all) # run after each after(:each) # run after all after(:all) ``` ### *Shared examples (not included here)* ### *Metadata and filters (not included here)* ## Mocking & Stubbing ***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 ```ruby # 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 ```ruby # 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/) ``` ## Custom Matchers (not included here)