thoughtbot--shoulda-matchers/README.md

1252 lines
34 KiB
Markdown
Raw Normal View History

# shoulda-matchers [![Gem Version][fury-badge]][fury] [![Build Status][travis-badge]][travis]
2012-03-09 16:57:31 +00:00
[Official Documentation][rubydocs]
2012-03-09 16:57:31 +00:00
shoulda-matchers provides Test::Unit- and RSpec-compatible one-liners that test
common Rails functionality. These tests would otherwise be much longer, more
complex, and error-prone.
2012-03-09 16:57:31 +00:00
## Installation
Simply add the following to your Gemfile:
```ruby
group :test do
gem 'shoulda-matchers'
end
```
2012-03-09 16:57:31 +00:00
shoulda-matchers automatically includes itself into your test framework. It will
mix in the appropriate matchers for ActiveRecord, ActiveModel, and
ActionController depending on the modules that are available at runtime. For
instance, in order to use the ActiveRecord matchers, ActiveRecord must be
available beforehand.
If your application is on Rails, everything should "just work", as
shoulda-matchers will most likely be declared after Rails in your Gemfile. If
your application is on another framework such as Sinatra or Padrino, you may
have a different setup, so you will want to ensure that you are requiring
shoulda-matchers after the components of Rails you are using.
2012-03-09 16:57:31 +00:00
## Usage
Different matchers apply to different parts of Rails:
* [ActiveModel](#activemodel-matchers)
* [ActiveRecord](#activerecord-matchers)
* [ActionController](#actioncontroller-matchers)
### ActiveModel Matchers
2013-12-05 16:38:30 +00:00
*Jump to: [allow_mass_assignment_of](#allow_mass_assignment_of), [allow_value / disallow_value](#allow_value--disallow_value), [ensure_inclusion_of](#ensure_inclusion_of), [ensure_exclusion_of](#ensure_exclusion_of), [ensure_length_of](#ensure_length_of), [have_secure_password](#have_secure_password), [validate_absence_of](#validate_absence_of), [validate_acceptance_of](#validate_acceptance_of), [validate_confirmation_of](#validate_confirmation_of), [validate_numericality_of](#validate_numericality_of), [validate_presence_of](#validate_presence_of), [validate_uniqueness_of](#validate_uniqueness_of)*
2013-12-03 18:02:42 +00:00
Note that all of the examples in this section are based on an ActiveRecord
model for simplicity, but these matchers will work just as well using an
ActiveModel model.
#### allow_mass_assignment_of
The `allow_mass_assignment_of` matcher tests usage of Rails 3's
`attr_accessible` and `attr_protected` macros, asserting that attributes can or
cannot be mass-assigned on a record.
2012-03-09 16:57:31 +00:00
```ruby
class Post < ActiveRecord::Base
attr_accessible :title
attr_accessible :published_status, as: :admin
end
class User < ActiveRecord::Base
attr_protected :encrypted_password
end
# RSpec
describe Post do
it { should allow_mass_assignment_of(:title) }
it { should allow_mass_assignment_of(:published_status).as(:admin) }
end
2012-03-09 16:57:31 +00:00
describe User do
it { should_not allow_mass_assignment_of(:encrypted_password) }
end
# Test::Unit
class PostTest < ActiveSupport::TestCase
should allow_mass_assignment_of(:title)
should allow_mass_assignment_of(:published_status).as(:admin)
end
class UserTest < ActiveSupport::TestCase
should_not allow_mass_assignment_of(:encrypted_password)
end
```
2012-03-09 16:57:31 +00:00
#### allow_value / disallow_value
2012-03-09 16:57:31 +00:00
The `allow_value` matcher tests usage of the `validates_format_of` validation.
It asserts that an attribute can be set to one or more values, succeeding if
none of the values cause the record to be invalid.
2012-03-09 16:57:31 +00:00
```ruby
class UserProfile < ActiveRecord::Base
validates_format_of :website_url, with: URI.regexp
validates_format_of :birthday_as_string,
with: /^(\d+)-(\d+)-(\d+)$/,
on: :create
validates_format_of :state,
with: /^(open|closed)$/,
message: 'State must be open or closed'
end
# RSpec
describe UserProfile do
2013-12-03 18:02:42 +00:00
it { should allow_value('http://foo.com', 'http://bar.com/baz').for(:website_url) }
it { should_not allow_value('asdfjkl').for(:website_url) }
2013-12-03 18:02:42 +00:00
it { should allow_value(:birthday_as_string).on(:create) }
it do
should allow_value('open', 'closed').
for(:state).
with_message('State must be open or closed')
end
end
# Test::Unit
class UserProfileTest < ActiveSupport::TestCase
2013-12-03 18:02:42 +00:00
should allow_value('http://foo.com', 'http://bar.com/baz').for(:website_url)
should_not allow_value('asdfjkl').for(:website_url)
should allow_value(:birthday_as_string).on(:create)
should allow_value('open', 'closed').
for(:state).
with_message('State must be open or closed')
end
```
2013-12-03 18:02:42 +00:00
**PLEASE NOTE:** Using `should_not` with `allow_value` completely negates the
assertion. This means that if multiple values are given to `allow_value`, the
matcher succeeds once it sees the *first* value that will cause the record to be
invalid:
```ruby
describe User do
# 'b' and 'c' will not be tested
it { should_not allow_value('a', 'b', 'c').for(:website_url) }
end
```
The `disallow_value` matcher is the exact opposite of `allow_value`. That is,
`should_not allow_value` can also be written `should disallow_value`, and
`should allow_value` can also be written `should_not disallow_value`. Because
of this, it carries the same caveat when multiple values are provided:
```ruby
describe User do
# 'b' and 'c' will not be tested
2013-12-03 18:02:42 +00:00
it { should disallow_value('a', 'b', 'c').for(:website_url) }
end
```
#### ensure_inclusion_of
The `ensure_inclusion_of` matcher tests usage of the `validates_inclusion_of`
validation, asserting that an attribute can take a set of values and cannot
take values outside of this set.
```ruby
class Issue < ActiveRecord::Base
validates_inclusion_of :state, in: %w(open resolved unresolved)
validates_inclusion_of :priority, in: 1..5
2013-12-03 18:02:42 +00:00
validates_inclusion_of :severity,
in: %w(low medium high),
message: 'Severity must be low, medium, or high'
end
# RSpec
describe Issue do
2013-12-03 18:02:42 +00:00
it { should ensure_inclusion_of(:state).in_array(%w(open resolved unresolved))
it { should ensure_inclusion_of(:state).in_range(1..5) }
it do
should ensure_inclusion_of(:severity).
in_array(%w(low medium high)).
with_message('Severity must be low, medium, or high')
end
end
# Test::Unit
class IssueTest < ActiveSupport::TestCase
should ensure_inclusion_of(:state).in_array(%w(open resolved unresolved))
should ensure_inclusion_of(:state).in_range(1..5)
2013-12-03 18:02:42 +00:00
should ensure_inclusion_of(:severity).
in_array(%w(low medium high)).
with_message('Severity must be low, medium, or high')
end
```
#### ensure_exclusion_of
The `ensure_exclusion_of` matcher tests usage of the `validates_exclusion_of`
validation, asserting that an attribute cannot take a set of values.
```ruby
class Game < ActiveRecord::Base
validates_exclusion_of :supported_os, in: ['Mac', 'Linux']
validates_exclusion_of :floors_with_enemies, in: 5..8
validates_exclusion_of :weapon,
in: ['pistol', 'paintball gun', 'stick'],
message: 'You chose a puny weapon'
end
# RSpec
describe Game do
2013-12-03 18:02:42 +00:00
it { should ensure_exclusion_of(:supported_os).in_array(['Mac', 'Linux']) }
it { should ensure_exclusion_of(:floors_with_enemies).in_range(5..8) }
it do
should ensure_exclusion_of(:weapon).
in_array(['pistol', 'paintball gun', 'stick']).
with_message('You chose a puny weapon')
end
end
# Test::Unit
class GameTest < ActiveSupport::TestCase
should ensure_exclusion_of(:supported_os).in_array(['Mac', 'Linux'])
should ensure_exclusion_of(:floors_with_enemies).in_range(5..8)
should ensure_exclusion_of(:weapon).
in_array(['pistol', 'paintball gun', 'stick']).
with_message('You chose a puny weapon')
end
```
#### ensure_length_of
The `ensure_length_of` matcher tests usage of the `validates_length_of` matcher.
```ruby
class User < ActiveRecord::Base
validates_length_of :bio, minimum: 15
validates_length_of :favorite_superhero, is: 6
validates_length_of :status_update, maximum: 140
validates_length_of :password, in: 5..30
validates_length_of :api_token,
in: 10..20,
2013-12-03 18:02:42 +00:00
message: 'API token must be in between 10 and 20 characters'
validates_length_of :secret_key, in: 15..100,
2013-12-03 18:02:42 +00:00
too_short: 'Secret key must be more than 15 characters',
too_long: 'Secret key cannot be more than 100 characters'
end
# RSpec
describe User do
it { should ensure_length_of(:bio).is_at_least(15) }
it { should ensure_length_of(:favorite_superhero).is_equal_to(6) }
it { should ensure_length_of(:status_update).is_at_most(140) }
it { should ensure_length_of(:password).is_at_least(5).is_at_most(30) }
it do
should ensure_length_of(:api_token).
2013-12-03 18:02:42 +00:00
is_at_least(10).
is_at_most(20).
message('Password must be in between 10 and 20 characters')
end
it do
should ensure_length_of(:secret_key).
2013-12-03 18:02:42 +00:00
is_at_least(15).
is_at_most(100).
with_short_message('Secret key must be more than 15 characters').
with_long_message('Secret key cannot be more than 100 characters')
end
end
# Test::Unit
class UserTest < ActiveSupport::TestCase
should ensure_length_of(:bio).is_at_least(15)
should ensure_length_of(:favorite_superhero).is_equal_to(6)
should ensure_length_of(:status_update).is_at_most(140)
should ensure_length_of(:password).is_at_least(5).is_at_most(30)
should ensure_length_of(:api_token).
2013-12-03 18:02:42 +00:00
is_at_least(15).
is_at_most(20).
message('Password must be in between 15 and 20 characters')
should ensure_length_of(:secret_key).
2013-12-03 18:02:42 +00:00
is_at_least(15).
is_at_most(100).
with_short_message('Secret key must be more than 15 characters').
with_long_message('Secret key cannot be more than 100 characters')
end
```
#### have_secure_password
The `have_secure_password` matcher tests usage of the `has_secure_password`
macro.
```ruby
class User < ActiveRecord::Base
has_secure_password
end
# RSpec
describe User do
2013-08-18 17:17:19 +00:00
it { should have_secure_password }
end
# Test::Unit
class UserTest < ActiveSupport::TestCase
should have_secure_password
end
```
2013-12-05 16:38:30 +00:00
#### validate_absence_of
The `validate_absence_of` matcher tests the usage of the
`validates_absence_of` validation.
```ruby
class Tank
include ActiveModel::Model
validates_absence_of :arms
validates_absence_of :legs,
message: "Tanks don't have legs."
end
# RSpec
describe Tank do
it { should validate_absence_of(:arms) }
it do
should validate_absence_of(:legs).
with_message("Tanks don't have legs.")
end
end
# Test::Unit
class TankTest < ActiveSupport::TestCase
should validate_absence_of(:arms)
should validate_absence_of(:legs).
with_message("Tanks don't have legs.")
end
```
#### validate_acceptance_of
The `validate_acceptance_of` matcher tests usage of the
`validates_acceptance_of` validation.
```ruby
2013-12-03 18:02:42 +00:00
class Registration < ActiveRecord::Base
validates_acceptance_of :eula
validates_acceptance_of :terms_of_service,
message: 'You must accept the terms of service'
end
# RSpec
describe Registration do
it { should validate_acceptance_of(:eula) }
it do
should validate_acceptance_of(:terms_of_service).
with_message('You must accept the terms of service')
end
end
# Test::Unit
class RegistrationTest < ActiveSupport::TestCase
should validate_acceptance_of(:eula)
should validate_acceptance_of(:terms_of_service).
with_message('You must accept the terms of service')
end
```
#### validate_confirmation_of
The `validate_confirmation_of` matcher tests usage of the
`validates_confirmation_of` validation.
```ruby
class User < ActiveRecord::Base
validates_confirmation_of :email
validates_confirmation_of :password, message: 'Please re-enter your password'
end
# RSpec
describe User do
it do
should validate_confirmation_of(:email)
end
it do
should validate_confirmation_of(:password).
with_message('Please re-enter your password')
end
end
# Test::Unit
class UserTest < ActiveSupport::TestCase
should validate_confirmation_of(:email)
should validate_confirmation_of(:password).
with_message('Please re-enter your password')
end
```
#### validate_numericality_of
The `validate_numericality_of` matcher tests usage of the
`validates_numericality_of` validation.
```ruby
class Person < ActiveRecord::Base
validates_numericality_of :gpa
validates_numericality_of :age, only_integer: true
validates_numericality_of :legal_age, greater_than: 21
validates_numericality_of :height, greater_than_or_equal_to: 55
validates_numericality_of :weight, equal_to: 150
validates_numericality_of :number_of_cars, less_than: 2
validates_numericality_of :birth_year, less_than_or_equal_to: 1987
validates_numericality_of :birth_day, odd: true
validates_numericality_of :birth_month, even: true
validates_numericality_of :number_of_dependents,
message: 'Number of dependents must be a number'
end
# RSpec
describe Person do
it { should validate_numericality_of(:gpa) }
it { should validate_numericality_of(:age).only_integer }
it { should validate_numericality_of(:legal_age).is_greater_than(21) }
it { should validate_numericality_of(:height).is_greater_than_or_equal_to(55) }
it { should validate_numericality_of(:weight).is_equal_to(150) }
it { should validate_numericality_of(:number_of_cars).is_less_than(2) }
it { should validate_numericality_of(:birth_year).is_less_than_or_equal_to(1987) }
it { should validate_numericality_of(:birth_day).odd }
it { should validate_numericality_of(:birth_month).even }
it do
should validate_numericality_of(:number_of_dependents).
with_message('Number of dependents must be a number' }
end
end
# Test::Unit
class PersonTest < ActiveSupport::TestCase
should validate_numericality_of(:gpa)
should validate_numericality_of(:age).only_integer
should validate_numericality_of(:legal_age).is_greater_than(21)
should validate_numericality_of(:height).is_greater_than_or_equal_to(55)
should validate_numericality_of(:weight).is_equal_to(150)
should validate_numericality_of(:number_of_cars).is_less_than(2)
should validate_numericality_of(:birth_year).is_less_than_or_equal_to(1987)
should validate_numericality_of(:birth_day).odd
should validate_numericality_of(:birth_month).even
should validate_numericality_of(:number_of_dependents).
with_message('Number of dependents must be a number')
end
```
#### validate_presence_of
The `validate_presence_of` matcher tests usage of the `validates_presence_of`
matcher.
```ruby
class Robot < ActiveRecord::Base
validates_presence_of :arms
validates_presence_of :legs, message: 'Robot has no legs'
end
# RSpec
describe Robot do
it { should validate_presence_of(:arms) }
it { should validate_presence_of(:legs).with_message('Robot has no legs') }
end
# Test::Unit
class RobotTest < ActiveSupport::TestCase
should validate_presence_of(:arms)
should validate_presence_of(:legs).with_message('Robot has no legs')
end
```
#### validate_uniqueness_of
The `validate_uniqueness_of` matcher tests usage of the
`validates_uniqueness_of` validation.
```ruby
class Post < ActiveRecord::Base
validates_uniqueness_of :permalink
validates_uniqueness_of :slug, scope: :user_id
validates_uniqueness_of :key, case_insensitive: true
validates_uniqueness_of :author_id, allow_nil: true
validates_uniqueness_of :title, message: 'Please choose another title'
end
# RSpec
describe Post do
it { should validate_uniqueness_of(:permalink) }
it { should validate_uniqueness_of(:slug).scoped_to(:journal_id) }
it { should validate_uniqueness_of(:key).case_insensitive }
it { should validate_uniqueness_of(:author_id).allow_nil }
it do
should validate_uniqueness_of(:title).
with_message('Please choose another title')
end
end
# Test::Unit
class PostTest < ActiveSupport::TestCase
should validate_uniqueness_of(:permalink)
should validate_uniqueness_of(:slug).scoped_to(:journal_id)
should validate_uniqueness_of(:key).case_insensitive
should validate_uniqueness_of(:author_id).allow_nil
should validate_uniqueness_of(:title).
with_message('Please choose another title')
end
```
2013-12-03 18:02:42 +00:00
**PLEASE NOTE:** This matcher works differently from other validation matchers.
Since the very concept of uniqueness depends on checking against a pre-existing
record in the database, this matcher will first use the model you're testing to
query for such a record, and if it can't find an existing one, it will create
one itself. Sometimes this step fails, especially if you have other validations
on the attribute you're testing (or, if you have database-level restrictions on
any attributes). In this case, the solution is to create a record before you use
`validate_uniqueness_of`.
For example, if you have this model:
```ruby
class Post < ActiveRecord::Base
validates_presence_of :permalink
validates_uniqueness_of :permalink
end
```
then you will need to test it like this:
```ruby
describe Post do
it do
Post.create!(title: 'This is the title')
should validate_uniqueness_of(:permalink)
end
end
```
### ActiveRecord Matchers
*Jump to: [accept_nested_attributes_for](#accept_nested_attributes_for), [belong_to](#belong_to), [have_many](#have_many), [have_one](#have_one), [have_and_belong_to_many](#have_and_belong_to_many), [have_db_column](#have_db_column), [have_db_index](#have_db_index), [have_readonly_attribute](#have_readonly_attribute), [serialize](#serialize)*
#### accept_nested_attributes_for
The `accept_nested_attributes_for` matcher tests usage of the
`accepts_nested_attributes_for` macro.
```ruby
class Car < ActiveRecord::Base
accept_nested_attributes_for :doors
accept_nested_attributes_for :mirrors, allow_destroy: true
accept_nested_attributes_for :windows, limit: 3
accept_nested_attributes_for :engine, update_only: true
end
# RSpec
describe Car do
it { should accept_nested_attributes_for(:doors) }
it { should accept_nested_attributes_for(:mirrors).allow_destroy(true) }
it { should accept_nested_attributes_for(:windows).limit(3) }
2013-12-03 18:02:42 +00:00
it { should accept_nested_attributes_for(:engine).update_only(true) }
end
# Test::Unit (using Shoulda)
class CarTest < ActiveSupport::TestCase
should accept_nested_attributes_for(:doors)
2013-12-03 18:02:42 +00:00
should accept_nested_attributes_for(:mirrors).allow_destroy(true)
should accept_nested_attributes_for(:windows).limit(3)
2013-12-03 18:02:42 +00:00
should accept_nested_attributes_for(:engine).update_only(true)
end
```
#### belong_to
The `belong_to` matcher tests your `belongs_to` associations.
```ruby
class Person < ActiveRecord::Base
belongs_to :organization
belongs_to :family, -> { where(everyone_is_perfect: false) }
belongs_to :previous_company, -> { order('hired_on desc') }
belongs_to :ancient_city, class_name: 'City'
belongs_to :great_country, foreign_key: 'country_id'
belongs_to :mental_institution, touch: true
belongs_to :world, dependent: :destroy
end
# RSpec
describe Person do
it { should belong_to(:organization) }
it { should belong_to(:family).conditions(everyone_is_perfect: false) }
it { should belong_to(:previous_company).order('hired_on desc') }
it { should belong_to(:ancient_city).class_name('City') }
it { should belong_to(:great_country).with_foreign_key('country_id') }
it { should belong_to(:mental_institution).touch(true) }
it { should belong_to(:world).dependent(:destroy) }
end
# Test::Unit
class PersonTest < ActiveSupport::TestCase
should belong_to(:organization)
should belong_to(:family).conditions(everyone_is_perfect: false)
should belong_to(:previous_company).order('hired_on desc')
should belong_to(:ancient_city).class_name('City')
should belong_to(:great_country).with_foreign_key('country_id')
should belong_to(:mental_institution).touch(true)
should belong_to(:world).dependent(:destroy)
end
```
2012-03-09 16:57:31 +00:00
#### have_many
2012-03-09 16:57:31 +00:00
The `have_many` matcher tests your `has_many` and `has_many :through` associations.
2012-03-09 16:57:31 +00:00
```ruby
class Person < ActiveRecord::Base
has_many :friends
has_many :acquaintances, through: :friends
has_many :job_offers, through: :friends, source: :opportunities
has_many :coins, -> { where(condition: 'mint') }
has_many :shirts, -> { order('color') }
has_many :hopes, class_name: 'Dream'
has_many :worries, foreign_key: 'worrier_id'
has_many :distractions, counter_cache: true
has_many :ideas, validate: false
has_many :topics_of_interest, touch: true
has_many :secret_documents, dependent: :destroy
end
# RSpec
describe Person do
it { should have_many(:friends) }
it { should have_many(:acquaintances).through(:friends) }
it { should have_many(:job_offers).through(:friends).source(:opportunities) }
it { should have_many(:coins).conditions(condition: 'mint') }
it { should have_many(:shirts).order('color') }
it { should have_many(:hopes).class_name('Dream') }
it { should have_many(:worries).with_foreign_key('worrier_id') }
it { should have_many(:ideas).validate(false) }
it { should have_many(:distractions).counter_cache(true) }
it { should have_many(:topics_of_interest).touch(true) }
it { should have_many(:secret_documents).dependent(:destroy) }
end
# Test::Unit
class PersonTest < ActiveSupport::TestCase
should have_many(:friends)
should have_many(:acquaintances).through(:friends)
should have_many(:job_offers).through(:friends).source(:opportunities)
should have_many(:coins).conditions(condition: 'mint')
should have_many(:shirts).order('color')
should have_many(:hopes).class_name('Dream')
should have_many(:worries).with_foreign_key('worrier_id')
should have_many(:ideas).validate(false)
should have_many(:distractions).counter_cache(true)
should have_many(:topics_of_interest).touch(true)
should have_many(:secret_documents).dependent(:destroy)
end
```
#### have_one
The `have_one` matcher tests your `has_one` and `has_one :through` associations.
```ruby
class Person < ActiveRecord::Base
has_one :partner
has_one :life, through: :partner
has_one :car, through: :partner, source: :vehicle
has_one :pet, -> { where('weight < 80') }
has_one :focus, -> { order('priority desc') }
has_one :chance, class_name: 'Opportunity'
has_one :job, foreign_key: 'worker_id'
has_one :parking_card, validate: false
has_one :contract, dependent: :nullify
end
# RSpec
describe Person do
it { should have_one(:partner) }
it { should have_one(:life).through(:partner) }
it { should have_one(:car).through(:partner).source(:vehicle) }
it { should have_one(:pet).conditions('weight < 80') }
it { should have_one(:focus).order('priority desc') }
it { should have_one(:chance).class_name('Opportunity') }
it { should have_one(:job).with_foreign_key('worker_id') }
it { should have_one(:parking_card).validate(false) }
it { should have_one(:contract).dependent(:nullify) }
end
# Test::Unit
class PersonTest < ActiveSupport::TestCase
should have_one(:partner)
should have_one(:life).through(:partner)
2013-12-03 18:02:42 +00:00
should have_one(:car).through(:partner).source(:vehicle)
should have_one(:pet).conditions('weight < 80')
should have_one(:focus).order('priority desc')
should have_one(:chance).class_name('Opportunity')
should have_one(:job).with_foreign_key('worker_id')
should have_one(:parking_card).validate(false)
should have_one(:contract).dependent(:nullify)
end
```
#### have_and_belong_to_many
The `have_and_belong_to_many` matcher tests your `has_and_belongs_to_many`
associations.
```ruby
class Person < ActiveRecord::Base
has_and_belongs_to_many :awards
has_and_belongs_to_many :issues, -> { where(difficulty: 'hard') }
has_and_belongs_to_many :projects, -> { order('time_spent') }
has_and_belongs_to_many :places_visited, class_name: 'City'
has_and_belongs_to_many :interviews, validate: false
end
# RSpec
describe Person do
it { should have_and_belong_to_many(:awards) }
it { should have_and_belong_to_many(:issues).conditions(difficulty: 'hard') }
it { should have_and_belong_to_many(:projects).order('time_spent') }
it { should have_and_belong_to_many(:places_visited).class_name('City') }
it { should have_and_belong_to_many(:interviews).validate(false) }
end
# Test::Unit
class PersonTest < ActiveSupport::TestCase
should have_and_belong_to_many(:awards)
should have_and_belong_to_many(:issues).conditions(difficulty: 'hard')
should have_and_belong_to_many(:projects).order('time_spent')
should have_and_belong_to_many(:places_visited).class_name('City')
should have_and_belong_to_many(:interviews).validate(false)
end
```
#### have_db_column
The `have_db_column` matcher tests that the table that backs your model
has a specific column.
```ruby
class CreatePhones < ActiveRecord::Migration
def change
create_table :phones do |t|
t.decimal :supported_ios_version
t.string :model, null: false
t.decimal :camera_aperture, precision: 1
2012-03-09 16:57:31 +00:00
end
end
end
2012-03-09 16:57:31 +00:00
# RSpec
describe Phone do
it { should have_db_column(:supported_ios_version) }
it { should have_db_column(:model).with_options(null: false) }
it do
should have_db_column(:camera_aperture).
of_type(:decimal).
with_options(precision: 1)
end
end
# Test::Unit
class PhoneTest < ActiveSupport::TestCase
should have_db_column(:supported_ios_version)
should have_db_column(:model).with_options(null: false)
should have_db_column(:camera_aperture).
of_type(:decimal).
with_options(precision: 1)
end
```
#### have_db_index
The `have_db_index` matcher tests that the table that backs your model has a
index on a specific column.
```ruby
class CreateBlogs < ActiveRecord::Migration
def change
create_table :blogs do |t|
t.integer :user_id, null: false
t.string :name, null: false
end
add_index :blogs, :user_id
add_index :blogs, :name, unique: true
end
end
# RSpec
describe Blog do
it { should have_db_index(:user_id) }
it { should have_db_index(:name).unique(true) }
end
# Test::Unit
class BlogTest < ActiveSupport::TestCase
should have_db_index(:user_id)
should have_db_index(:name).unique(true)
end
```
#### have_readonly_attribute
2012-03-09 16:57:31 +00:00
The `have_readonly_attribute` matcher tests usage of the `attr_readonly` macro.
2012-03-09 16:57:31 +00:00
```ruby
class User < ActiveRecord::Base
attr_readonly :password
end
# RSpec
describe User do
it { should have_readonly_attribute(:password) }
end
# Test::Unit
class UserTest < ActiveSupport::TestCase
should have_readonly_attribute(:password)
end
```
#### serialize
The `serialize` matcher tests usage of the `serialize` macro.
```ruby
class ProductOptionsSerializer
def load(string)
# ...
end
def dump(options)
# ...
end
end
class Product < ActiveRecord::Base
serialize :customizations
serialize :specifications, ProductSpecsSerializer
serialize :options, ProductOptionsSerializer.new
end
# RSpec
describe Product do
it { should serialize(:customizations) }
it { should serialize(:specifications).as(ProductSpecsSerializer) }
it { should serialize(:options).as_instance_of(ProductOptionsSerializer) }
end
2013-12-03 18:02:42 +00:00
# Test::Unit
class ProductTest < ActiveSupport::TestCase
should serialize(:customizations)
should serialize(:specifications).as(ProductSpecsSerializer)
should serialize(:options).as_instance_of(ProductOptionsSerializer)
end
```
### ActionController Matchers
2013-12-03 18:02:42 +00:00
*Jump to: [filter_param](#filter_param), [redirect_to](#redirect_to), [render_template](#render_template), [render_with_layout](#render_with_layout), [rescue_from](#rescue_from), [respond_with](#respond_with), [route](#route), [set_session](#set_session), [set_the_flash](#set_the_flash)*
#### filter_param
The `filter_param` matcher tests parameter filtering configuration.
```ruby
class MyApplication < Rails::Application
config.filter_parameters << :secret_key
end
# RSpec
describe ApplicationController do
it { should filter_param(:secret_key) }
end
2013-12-03 18:02:42 +00:00
# Test::Unit
class ApplicationControllerTest < ActionController::TestCase
should filter_param(:secret_key)
end
```
#### redirect_to
The `redirect_to` matcher tests that an action redirects to a certain location.
In a test suite using RSpec, it is very similar to rspec-rails's `redirect_to`
matcher. In a test suite using Test::Unit / Shoulda, it provides a more
expressive syntax over `assert_redirected_to`.
```ruby
class PostsController < ApplicationController
def show
redirect_to :index
end
end
# RSpec
describe PostsController do
describe 'GET #list' do
before { get :list }
it { should redirect_to(posts_path) }
end
end
# Test::Unit
class PostsControllerTest < ActionController::TestCase
context 'GET #list' do
setup { get :list }
should redirect_to { posts_path }
end
end
```
#### render_template
The `render_template` matcher tests that an action renders a template.
In RSpec, it is very similar to rspec-rails's `render_template` matcher.
In Test::Unit, it provides a more expressive syntax over `assert_template`.
```ruby
class PostsController < ApplicationController
def show
end
end
# RSpec
describe PostsController do
describe 'GET #show' do
before { get :show }
it { should render_template('show') }
end
end
# Test::Unit
class PostsControllerTest < ActionController::TestCase
context 'GET #show' do
setup { get :show }
should render_template('show')
end
end
```
#### render_with_layout
The `render_with_layout` matcher tests that an action is rendered with a certain
layout.
```ruby
class PostsController < ApplicationController
def show
render layout: 'posts'
end
end
# RSpec
describe PostsController do
describe 'GET #show' do
before { get :show }
it { should render_with_layout('posts') }
end
end
# Test::Unit
class PostsControllerTest < ActionController::TestCase
context 'GET #show' do
setup { get :show }
should render_with_layout('posts')
end
end
```
#### rescue_from
The `rescue_from` matcher tests usage of the `rescue_from` macro.
```ruby
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, with: :handle_not_found
private
def handle_not_found
# ...
end
end
# RSpec
describe ApplicationController do
it do
should rescue_from(ActiveRecord::RecordNotFound).
with(:handle_not_found)
end
end
# Test::Unit
class ApplicationControllerTest < ActionController::TestCase
should rescue_from(ActiveRecord::RecordNotFound).
with(:handle_not_found)
end
```
#### respond_with
The `respond_with` matcher tests that an action responds with a certain status
code.
```ruby
class PostsController < ApplicationController
def index
render status: 403
end
def show
render status: :locked
end
def destroy
render status: 508
end
end
# RSpec
describe PostsController do
describe 'GET #index' do
before { get :index }
it { should respond_with(403) }
end
describe 'GET #show' do
before { get :show }
it { should respond_with(:locked) }
end
describe 'DELETE #destroy' do
before { delete :destroy }
it { should respond_with(500..600) }
end
end
# Test::Unit
class PostsControllerTest < ActionController::TestCase
context 'GET #index' do
setup { get :index }
should respond_with(403)
end
context 'GET #show' do
setup { get :show }
should respond_with(:locked)
end
context 'DELETE #destroy' do
setup { delete :destroy }
should respond_with(500..600)
end
end
```
#### route
The `route` matcher tests that a route resolves to a controller, action, and
params; and that the controller, action, and params generates the same route. For
an RSpec suite, this is like using a combination of `route_to` and
`be_routable`. For a Test::Unit suite, it provides a more expressive syntax
over `assert_routing`.
```ruby
My::Application.routes.draw do
2013-12-03 18:02:42 +00:00
get '/posts', controller: 'posts', action: 'index'
get '/posts/:id' => 'posts#show'
end
# RSpec
describe 'Routing' do
2013-12-03 18:02:42 +00:00
it { should route(:get, '/posts').to(controller: 'posts', action: 'index') }
it { should route(:get, '/posts/1').to('posts#show', id: 1) }
end
# Test::Unit
class RoutesTest < ActionController::IntegrationTest
2013-12-03 18:02:42 +00:00
should route(:get, '/posts').to(controller: 'posts', action: 'index')
should route(:get, '/posts/1').to('posts#show', id: 1)
end
```
#### set_session
The `set_session` matcher asserts that a key in the `session` hash has been set
to a certain value.
```ruby
class PostsController < ApplicationController
def show
session[:foo] = 'bar'
end
end
# RSpec
describe PostsController do
describe 'GET #show' do
before { get :show }
it { should set_session(:foo).to('bar') }
it { should_not set_session(:baz) }
end
end
# Test::Unit
class PostsControllerTest < ActionController::TestCase
context 'GET #show' do
setup { get :show }
should set_session(:foo).to('bar')
should_not set_session(:baz)
end
end
```
#### set_the_flash
The `set_the_flash` matcher asserts that a key in the `flash` hash is set to a
certain value.
```ruby
class PostsController < ApplicationController
def index
flash[:foo] = 'A candy bar'
end
def show
flash.now[:foo] = 'bar'
end
def destroy
end
end
# RSpec
describe PostsController do
describe 'GET #index' do
before { get :index }
it { should set_the_flash.to('bar') }
it { should set_the_flash.to(/bar/) }
it { should set_the_flash[:foo].to('bar') }
it { should_not set_the_flash[:baz] }
end
describe 'GET #show' do
before { get :show }
it { should set_the_flash.now }
it { should set_the_flash[:foo].now }
it { should set_the_flash[:foo].to('bar').now }
end
describe 'DELETE #destroy' do
before { delete :destroy }
it { should_not set_the_flash }
end
end
# Test::Unit
class PostsControllerTest < ActionController::TestCase
context 'GET #index' do
setup { get :index }
should set_the_flash.to('bar')
should set_the_flash.to(/bar/)
should set_the_flash[:foo].to('bar')
should_not set_the_flash[:baz]
end
context 'GET #show' do
setup { get :show }
should set_the_flash.now
should set_the_flash[:foo].now
should set_the_flash[:foo].to('bar').now
end
context 'DELETE #destroy' do
setup { delete :destroy }
should_not set_the_flash
end
end
```
## Versioning
shoulda-matchers follows Semantic Versioning 2.0 as defined at
<http://semver.org>.
2012-03-09 16:57:31 +00:00
## Credits
shoulda-matchers is maintained and funded by [thoughtbot][community]. Thank you
to all the [contributors][contributors].
2012-03-09 16:57:31 +00:00
## License
shoulda-matchers is copyright © 2006-2013 thoughtbot, inc. It is free software,
and may be redistributed under the terms specified in the
[MIT-LICENSE](MIT-LICENSE) file.
[fury-badge]: https://badge.fury.io/rb/shoulda-matchers.png
[fury]: http://badge.fury.io/rb/shoulda-matchers
[travis-badge]: https://secure.travis-ci.org/thoughtbot/shoulda-matchers.png?branch=master
[travis]: http://travis-ci.org/thoughtbot/shoulda-matchers
[rubydocs]: http://rubydoc.info/github/thoughtbot/shoulda-matchers/master/frames
[community]: http://thoughtbot.com/community
[contributors]: https://github.com/thoughtbot/shoulda-matchers/contributors