Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save hoasung01/f72b124a5c6075b94d21 to your computer and use it in GitHub Desktop.

Select an option

Save hoasung01/f72b124a5c6075b94d21 to your computer and use it in GitHub Desktop.

Revisions

  1. Dapeng Li created this gist Mar 9, 2013.
    374 changes: 374 additions & 0 deletions rspec_cheatsheet_for_zombies.markdown
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,374 @@
    ## 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)