public
Last active

RSpec Matchers

  • Download Gist
capybara.md
Markdown

Capybara

save_and_open_page

Matchers

have_button(locator)

have_checked_field(locator)

have_css('p#foo', :count => 4)
have_css('li', :text => 'Horse', :visible => true)

have_field('Name', :with => 'Jonas')
have_field('Email', :type => 'email')

have_link(locator, options = {})

have_select('Language', :selected => 'German')
have_select('Language', :selected => ['English', 'German'])
have_select('Language', :options => ['English', 'German', 'Spanish'])
have_select('Language', :with_options => ['English', 'German'])

have_selector('p#foo')
have_no_selector('p#bar')
have_selector(:xpath, './/p[@id="foo"]')
have_selector(:foo)
have_selector('p#foo', :count => 4)
have_selector('li', :text => 'Horse', :visible => true)

have_table('People', :rows => [['Jonas', '24'], ['Peter', '32']])

have_text(content)
have_content(content)

have_unchecked_field?(locator)

have_xpath('.//p[@id="foo"]')
have_xpath('.//p[@id="foo"]', :count => 4)
have_xpath('.//li', :text => 'Horse', :visible => true)

Actions

attach_file(locator, path)
page.attach_file(locator, '/path/to/file.png')

check(locator)
page.check('German')

choose(locator)
page.choose('Male')

click_button(locator)
click_link(locator)
click_link_or_button(locator) (also: #click_on)

fill_in(locator, options = {})
page.fill_in 'Name', :with => 'Bob'

select(value, options = {})
page.select 'March', :from => 'Month'

uncheck(locator)
page.uncheck('German')

unselect(value, options = {})
page.unselect 'March', :from => 'Month'

Session

current_host
current_path
current_url
evaluate_script(script)
execute_script(script)
html
reset! # (also: #cleanup!, #reset_session!)
response_headers  # Returns a hash of response headers.
save_and_open_page
save_page
source # (also: #body)
status_code
visit(url)
within(*args)
within_fieldset(locator)
within_frame(frame_id)
within_table(locator)
within_window(handle, &blk)

Finders

all([kind], locator, options)
all(:css, 'a#person_123')
all(:xpath, '//a[@id="person_123"]')
all("a#person_123")
all('a', :text => 'Home')
all('#menu li', :visible => true)

find('#foo').find('.bar')
find(:xpath, '//div[contains(., "bar")]')
find('li', :text => 'Quox').click_link('Delete')

find_button(locator)
find_by_id(id)
find_field(locator) # aka field_labeled
find_link(locator)
first([kind], locator, options)
paperclip-matchers.md
Markdown

Paperclip with Shoulda

docs

describe User do
  it { should have_attached_file(:avatar) }
  it { should validate_attachment_presence(:avatar) }
  it { should validate_attachment_content_type(:avatar).
                allowing('image/png', 'image/gif').
                rejecting('text/plain', 'text/xml') }
  it { should validate_attachment_size(:avatar).
                less_than(2.megabytes) }
end
rails-matchers.md
Markdown

RSpec-Rails

Models

obj.should be_valid
obj.should have(n).errors_on(:field)
obj.errors.on(:field).should == "is required"

General

response.should be_valid
response.should_not be_valid

response.should be_success
response.should be_redirect

response.should redirect_to("path/to/action")
response.should redirect_to("http://test.host/path/to/action")
response.should redirect_to(:action => 'list')

response.should render_template('list')
response.should render_template('same_controller/list')
response.should render_template('other_controller/list')

# partials
response.should render_template('_a_partial')
response.should render_template('same_controller/_a_partial')
response.should render_template('other_controller/_a_partial')

Routing

"path".should route_to(expected) # assumes GET
{ :get => "path" }.should route_to(expected)
{ :put => "path" }.should route_to(expected)

{ "path" }.should_not be_routable # assumes GET
{ :get => "path" }.should_not be_routable
{ :put => "path" }.should_not be_routable

Content

response.should have_tag("div", "some text")
person_address_tag.should have_tag("input#person_address") # in a helper

response.should have_tag("div#form") do
  with_tag("input#person_name[name=?]", "person[name]")
end

response.should have_tag("div#1") do
  without_tag("span", "some text that shouldn't be there")
end

response.should include_text("This text will be in the actual string")

Mail

response.should send_email(*args, &block)
rspec-expectations.md
Markdown

RSpec Expectations

Built-in matchers

have(n)
have_at_least(n)
have_at_most(n)

Equivalence

actual.should eq(expected)  # passes if actual == expected
actual.should == expected   # passes if actual == expected
actual.should eql(expected) # passes if actual.eql?(expected)

Identity

actual.should be(expected)    # passes if actual.equal?(expected)
actual.should equal(expected) # passes if actual.equal?(expected)

Comparisons

actual.should be >  expected
actual.should be >= expected
actual.should be <= expected
actual.should be <  expected
actual.should be_within(delta).of(expected)

Regular expressions

actual.should =~ /expression/
actual.should match(/expression/)

Types/classes

actual.should be_an_instance_of(expected)
actual.should be_a_kind_of(expected)

Truthiness

actual.should be_true  # passes if actual is truthy (not nil or false)
actual.should be_false # passes if actual is falsy (nil or false)
actual.should be_nil   # passes if actual is nil

Expecting errors

expect { ... }.to raise_error
expect { ... }.to raise_error(ErrorClass)
expect { ... }.to raise_error("message")
expect { ... }.to raise_error(ErrorClass, "message")

Expecting throws

expect { ... }.to throw_symbol
expect { ... }.to throw_symbol(:symbol)
expect { ... }.to throw_symbol(:symbol, 'value')

Predicate matchers

actual.should be_xxx         # passes if actual.xxx?
actual.should have_xxx(:arg) # passes if actual.has_xxx?(:arg)

{:a => "A"}.should have_key(:a) # => {:a => "A"}.has_key?(:a) | passes
{:a => "A"}.should have_key(:b) # => {:a => "A"}.has_key?(:b) | fails

Change

expect {
  team.add_player(player) 
}.to change(roster, :count)

expect {
  team.add_player(player) 
}.to change(roster, :count).by(1)

expect {
  team.add_player(player) 
}.to change(roster, :count).by_at_least(1)

expect {
  team.add_player(player)
}.to change(roster, :count).by_at_most(1)    

string = "string"
expect {
  string.reverse!
}.to change { string }.from("string").to("gnirts")

expect {
  person.happy_birthday
}.to change(person, :birthday).from(32).to(33)

expect {
  employee.develop_great_new_social_networking_app
}.to change(employee, :title).from("Mail Clerk").to("CEO")

expect {
  doctor.leave_office
}.to change(doctor, :sign).from(/is in/).to(/is out/)

user = User.new(:type => "admin")
expect {
  user.symbolize_type
}.to change(user, :type).from(String).to(Symbol)

Ranges (Ruby >= 1.9 only)

(1..10).should cover(3)

Collection membership

actual.should include(expected)
actual.should start_with(expected)
actual.should end_with(expected)

Examples

[1,2,3].should include(1)
[1,2,3].should include(1, 2)
[1,2,3].should start_with(1)
[1,2,3].should start_with(1,2)
[1,2,3].should end_with(3)
[1,2,3].should end_with(2,3)
{:a => 'b'}.should include(:a => 'b')
"this string".should include("is str")
"this string".should start_with("this")
"this string".should end_with("ring")
shoulda-matchers.md
Markdown

Shoulda

ActiveRecord Matchers

Matchers to test associations:

describe Post do
  it { should belong_to(:user) }
  it { should have_many(:tags).through(:taggings) }
end

describe User do
  it { should have_many(:posts) }
end

it { should have_and_belong_to_many(:posts) }

it { should_not have_db_column(:admin).of_type(:boolean) }
it { should have_db_column(:salary).
              of_type(:decimal).
              with_options(:precision => 10, :scale => 2) }
it { should have_db_column(:id).with_options(:primary => true) }

it { should have_db_index(:age) }
it { should have_db_index([:commentable_type, :commentable_id]) }
it { should have_db_index(:ssn).unique(true) }

it { should have_many(:friends) }
it { should have_many(:enemies).through(:friends) }
it { should have_many(:enemies).dependent(:destroy) }

it { should have_one(:god) }

it { should have_readonly_attribute(:password) }

it { should query_the_database(4.times).when_calling(:complicated_method) } 
it { should query_the_database(4.times).or_less.when_calling(:complicated_method) } 
it { should_not query_the_database.when_calling(:complicated_method) }

ActiveModel Matchers

Matchers to test validations and mass assignments:

describe Post do
  it { should validate_uniqueness_of(:title) }
  it { should validate_presence_of(:body).with_message(/wtf/) }
  it { should validate_presence_of(:title) }
  it { should validate_numericality_of(:user_id) }
  it { should validate_numericality_of(:user_id).only_integer }
  it { should validate_acceptance_of(:eula) }

  it { should validate_format_of(:name).
                  with('12345').
                  with_message(/is not optional/) }
  it { should validate_format_of(:name).
                not_with('12D45').
                with_message(/is not optional/) }
  it { should validate_numericality_of(:age) }
  it { should validate_confirmation_of(:password) }


  # validates_uniqueness_of requires an entry to be in the database already
  it "validates uniqueness of title" do
    Post.create!(title: "My Awesome Post", body: "whatever")
    should validate_uniqueness_of(:title)
  end
end

describe User do
  it { should_not allow_value("blah").for(:email) }
  it { should allow_value("a@b.com").for(:email) }
  it { should ensure_inclusion_of(:age).in_range(1..100) }
  it { should ensure_exclusion_of(:age).in_range(30..60) }
  it { should_not allow_mass_assignment_of(:password) }
end

it { should ensure_length_of(:password).
              is_at_least(6).
              is_at_most(20) }
it { should ensure_length_of(:name).
              is_at_least(3).
              with_short_message(/not long enough/) }
it { should ensure_length_of(:ssn).
              is_equal_to(9).
              with_message(/is invalid/) }

ActionController Matchers

Matchers to test common patterns:

describe PostsController, "#show" do
  context "for a fictional user" do
    before do
      get :show, :id => 1
    end

    it { should assign_to(:user) }
    it { should respond_with(:success) }
    it { should render_template(:show) }
    it { should_not set_the_flash }
    it { should set_the_flash[:alert].to("Password doesn't match") }
  end
end

# other examples
it { should assign_to(:user) }
it { should_not assign_to(:user) }
it { should assign_to(:user).with_kind_of(User) }
it { should assign_to(:user).with(@user) }

it { should render_with_layout }
it { should render_with_layout(:special) }
it { should_not render_with_layout }

it { should respond_with(:success)  }
it { should respond_with(:redirect) }
it { should respond_with(:missing)  }
it { should respond_with(:error)    }
it { should respond_with(501)       }

it { should respond_with_content_type(:xml)  }
it { should respond_with_content_type(:csv)  }
it { should respond_with_content_type(:atom) }
it { should respond_with_content_type(:yaml) }
it { should respond_with_content_type(:text) }
it { should respond_with_content_type('application/rss+xml') }
it { should respond_with_content_type(/json/) }

it { should set_session(:message) }
it { should set_session(:user_id).to(@user.id) }
it { should_not set_session(:user_id) }

it { should set_the_flash }
it { should set_the_flash.to("Thank you for placing this order.") }
it { should set_the_flash.to(/created/i) }
it { should set_the_flash.to(/logged in/i).now }
it { should_not set_the_flash }

it { should filter_param(:password) }

ActionMailer Matchers

it { should have_sent_email.with_subject(/is spam$/) }
it { should have_sent_email.from('do-not-reply@example.com') }
it { should have_sent_email.with_body(/is spam\./) }
it { should have_sent_email.to('myself@me.com') }
it { should have_sent_email.with_part('text/html', /HTML spam/) }
it { should have_sent_email.with_subject(/spam/).
                            from('do-not-reply@example.com').
                            with_body(/spam/).
                            to('myself@me.com') }

it {should have_sent_email.to {@user.email} }
it { should have_sent_email.reply_to([user, other]) }

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.