Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save darkn3rd/6b56d9a323a99cf32b9fdcba7028d877 to your computer and use it in GitHub Desktop.
Save darkn3rd/6b56d9a323a99cf32b9fdcba7028d877 to your computer and use it in GitHub Desktop.
Cheat sheet for RSpec based on CodeSchool's "Testing with RSpec" course

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

$ 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

$ rspec --init
  create spec/spec_helper.rb
  create .rspec

Initialize RSpec in Rails

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

Configuration in Rails

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 specs

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

Matchers

# 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

#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 & Tags

hooks

# 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

# 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

# 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)

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