2013-10-23 15:27:08 -04:00
|
|
|
# shoulda-matchers [![Gem Version][fury-badge]][fury] [![Build Status][travis-badge]][travis]
|
2012-03-09 11:57:31 -05:00
|
|
|
|
2013-10-23 15:27:08 -04:00
|
|
|
[Official Documentation][rubydocs]
|
2012-03-09 11:57:31 -05:00
|
|
|
|
2013-10-23 15:27:08 -04: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 11:57:31 -05:00
|
|
|
|
2013-10-23 15:27:08 -04:00
|
|
|
## Installation
|
|
|
|
|
2014-01-10 20:36:52 -05:00
|
|
|
### RSpec
|
|
|
|
|
|
|
|
Include the gem in your Gemfile:
|
2013-10-23 15:27:08 -04:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
group :test do
|
|
|
|
gem 'shoulda-matchers'
|
|
|
|
end
|
|
|
|
```
|
2012-03-09 11:57:31 -05:00
|
|
|
|
2014-01-10 20:36:52 -05:00
|
|
|
### Test::Unit
|
|
|
|
|
|
|
|
shoulda-matchers was originally a component of
|
|
|
|
[Shoulda](http://github.com/thoughtbot/shoulda) -- it's what provides the nice
|
|
|
|
`should` syntax which is demonstrated below. For this reason, include it in
|
|
|
|
your Gemfile instead:
|
|
|
|
|
|
|
|
```ruby
|
|
|
|
group :test do
|
|
|
|
gem 'shoulda'
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
|
|
|
### Non-Rails apps
|
|
|
|
|
|
|
|
Once it is loaded, 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.
|
2013-12-18 22:06:29 -05:00
|
|
|
|
|
|
|
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
|
2014-01-10 20:36:52 -05:00
|
|
|
shoulda-matchers after the components of Rails you are using. For instance,
|
|
|
|
if you wanted to use and test against ActiveModel, you'd say:
|
|
|
|
|
|
|
|
```ruby
|
|
|
|
gem 'activemodel'
|
|
|
|
gem 'shoulda-matchers'
|
|
|
|
```
|
|
|
|
|
|
|
|
and not:
|
|
|
|
|
|
|
|
```ruby
|
|
|
|
gem 'shoulda-matchers'
|
|
|
|
gem 'activemodel'
|
|
|
|
```
|
2012-03-09 11:57:31 -05:00
|
|
|
|
2013-10-23 15:27:08 -04:00
|
|
|
## Usage
|
|
|
|
|
|
|
|
Different matchers apply to different parts of Rails:
|
|
|
|
|
|
|
|
* [ActiveModel](#activemodel-matchers)
|
|
|
|
* [ActiveRecord](#activerecord-matchers)
|
|
|
|
* [ActionController](#actioncontroller-matchers)
|
|
|
|
|
|
|
|
### ActiveModel Matchers
|
|
|
|
|
2013-12-05 11:38:30 -05: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-10-23 15:27:08 -04:00
|
|
|
|
2013-12-03 13:02:42 -05: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.
|
|
|
|
|
2013-10-23 15:27:08 -04:00
|
|
|
#### 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 11:57:31 -05:00
|
|
|
|
2012-11-04 22:55:51 -05:00
|
|
|
```ruby
|
2013-10-23 15:27:08 -04:00
|
|
|
class Post < ActiveRecord::Base
|
|
|
|
attr_accessible :title
|
|
|
|
attr_accessible :published_status, as: :admin
|
|
|
|
end
|
|
|
|
|
|
|
|
class User < ActiveRecord::Base
|
|
|
|
attr_protected :encrypted_password
|
|
|
|
end
|
|
|
|
|
|
|
|
# RSpec
|
2012-11-04 22:55:51 -05:00
|
|
|
describe Post do
|
2013-10-23 15:27:08 -04:00
|
|
|
it { should allow_mass_assignment_of(:title) }
|
|
|
|
it { should allow_mass_assignment_of(:published_status).as(:admin) }
|
2012-11-04 22:55:51 -05:00
|
|
|
end
|
2012-03-09 11:57:31 -05:00
|
|
|
|
2012-11-04 22:55:51 -05:00
|
|
|
describe User do
|
2013-10-23 15:27:08 -04:00
|
|
|
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)
|
2012-11-04 22:55:51 -05:00
|
|
|
end
|
|
|
|
```
|
2012-03-09 11:57:31 -05:00
|
|
|
|
2013-10-23 15:27:08 -04:00
|
|
|
#### allow_value / disallow_value
|
2012-03-09 11:57:31 -05:00
|
|
|
|
2013-10-23 15:27:08 -04: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 11:57:31 -05:00
|
|
|
|
2012-11-04 22:55:51 -05:00
|
|
|
```ruby
|
2013-10-23 15:27:08 -04:00
|
|
|
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 13:02:42 -05: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-10-23 15:27:08 -04:00
|
|
|
|
2013-12-03 13:02:42 -05:00
|
|
|
it { should allow_value(:birthday_as_string).on(:create) }
|
2013-10-23 15:27:08 -04:00
|
|
|
|
|
|
|
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 13:02:42 -05:00
|
|
|
should allow_value('http://foo.com', 'http://bar.com/baz').for(:website_url)
|
2013-10-23 15:27:08 -04:00
|
|
|
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 13:02:42 -05:00
|
|
|
**PLEASE NOTE:** Using `should_not` with `allow_value` completely negates the
|
2013-10-23 15:27:08 -04:00
|
|
|
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 13:02:42 -05:00
|
|
|
it { should disallow_value('a', 'b', 'c').for(:website_url) }
|
2013-10-23 15:27:08 -04:00
|
|
|
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 13:02:42 -05:00
|
|
|
|
2013-10-23 15:27:08 -04: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 13:02:42 -05:00
|
|
|
it { should ensure_inclusion_of(:state).in_array(%w(open resolved unresolved))
|
|
|
|
it { should ensure_inclusion_of(:state).in_range(1..5) }
|
2013-10-23 15:27:08 -04:00
|
|
|
|
|
|
|
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 13:02:42 -05:00
|
|
|
|
2013-10-23 15:27:08 -04: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 13:02:42 -05:00
|
|
|
it { should ensure_exclusion_of(:supported_os).in_array(['Mac', 'Linux']) }
|
2013-10-23 15:27:08 -04:00
|
|
|
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 13:02:42 -05:00
|
|
|
message: 'API token must be in between 10 and 20 characters'
|
2013-10-23 15:27:08 -04:00
|
|
|
|
|
|
|
validates_length_of :secret_key, in: 15..100,
|
2013-12-03 13:02:42 -05:00
|
|
|
too_short: 'Secret key must be more than 15 characters',
|
|
|
|
too_long: 'Secret key cannot be more than 100 characters'
|
2013-10-23 15:27:08 -04:00
|
|
|
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 13:02:42 -05:00
|
|
|
is_at_least(10).
|
|
|
|
is_at_most(20).
|
|
|
|
message('Password must be in between 10 and 20 characters')
|
2013-10-23 15:27:08 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it do
|
|
|
|
should ensure_length_of(:secret_key).
|
2013-12-03 13:02:42 -05: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')
|
2013-10-23 15:27:08 -04:00
|
|
|
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 13:02:42 -05:00
|
|
|
is_at_least(15).
|
|
|
|
is_at_most(20).
|
|
|
|
message('Password must be in between 15 and 20 characters')
|
2013-10-23 15:27:08 -04:00
|
|
|
|
|
|
|
should ensure_length_of(:secret_key).
|
2013-12-03 13:02:42 -05: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')
|
2012-11-04 22:55:51 -05:00
|
|
|
end
|
2013-10-23 15:27:08 -04:00
|
|
|
```
|
|
|
|
|
|
|
|
#### have_secure_password
|
2012-11-04 22:55:51 -05:00
|
|
|
|
2013-10-23 15:27:08 -04:00
|
|
|
The `have_secure_password` matcher tests usage of the `has_secure_password`
|
|
|
|
macro.
|
|
|
|
|
|
|
|
```ruby
|
|
|
|
class User < ActiveRecord::Base
|
|
|
|
has_secure_password
|
|
|
|
end
|
|
|
|
|
|
|
|
# RSpec
|
2012-11-04 22:55:51 -05:00
|
|
|
describe User do
|
2013-08-18 13:17:19 -04:00
|
|
|
it { should have_secure_password }
|
2012-11-04 22:55:51 -05:00
|
|
|
end
|
2013-10-23 15:27:08 -04:00
|
|
|
|
|
|
|
# Test::Unit
|
|
|
|
class UserTest < ActiveSupport::TestCase
|
|
|
|
should have_secure_password
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2013-12-05 11:38:30 -05: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
|
|
|
|
```
|
|
|
|
|
2013-10-23 15:27:08 -04:00
|
|
|
#### validate_acceptance_of
|
|
|
|
|
|
|
|
The `validate_acceptance_of` matcher tests usage of the
|
|
|
|
`validates_acceptance_of` validation.
|
|
|
|
|
|
|
|
```ruby
|
2013-12-03 13:02:42 -05:00
|
|
|
class Registration < ActiveRecord::Base
|
2013-10-23 15:27:08 -04:00
|
|
|
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 13:02:42 -05: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`.
|
2013-10-23 15:27:08 -04:00
|
|
|
|
|
|
|
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) }
|
2013-12-01 07:43:43 -05:00
|
|
|
it { should accept_nested_attributes_for(:mirrors).allow_destroy(true) }
|
2013-10-23 15:27:08 -04:00
|
|
|
it { should accept_nested_attributes_for(:windows).limit(3) }
|
2013-12-03 13:02:42 -05:00
|
|
|
it { should accept_nested_attributes_for(:engine).update_only(true) }
|
2013-10-23 15:27:08 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
# Test::Unit (using Shoulda)
|
|
|
|
class CarTest < ActiveSupport::TestCase
|
|
|
|
should accept_nested_attributes_for(:doors)
|
2013-12-03 13:02:42 -05:00
|
|
|
should accept_nested_attributes_for(:mirrors).allow_destroy(true)
|
2013-10-23 15:27:08 -04:00
|
|
|
should accept_nested_attributes_for(:windows).limit(3)
|
2013-12-03 13:02:42 -05:00
|
|
|
should accept_nested_attributes_for(:engine).update_only(true)
|
2013-10-23 15:27:08 -04:00
|
|
|
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-11-04 22:55:51 -05:00
|
|
|
```
|
2012-03-09 11:57:31 -05:00
|
|
|
|
2013-10-23 15:27:08 -04:00
|
|
|
#### have_many
|
2012-03-09 11:57:31 -05:00
|
|
|
|
2013-10-23 15:27:08 -04:00
|
|
|
The `have_many` matcher tests your `has_many` and `has_many :through` associations.
|
2012-03-09 11:57:31 -05:00
|
|
|
|
2012-11-04 22:55:51 -05:00
|
|
|
```ruby
|
2013-10-23 15:27:08 -04:00
|
|
|
class Person < ActiveRecord::Base
|
|
|
|
has_many :friends
|
|
|
|
has_many :acquaintances, through: :friends
|
2013-11-27 13:18:46 -05:00
|
|
|
has_many :job_offers, through: :friends, source: :opportunities
|
2013-10-23 15:27:08 -04:00
|
|
|
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) }
|
2013-11-27 13:18:46 -05:00
|
|
|
it { should have_many(:job_offers).through(:friends).source(:opportunities) }
|
2013-10-23 15:27:08 -04:00
|
|
|
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)
|
2013-11-27 13:18:46 -05:00
|
|
|
should have_many(:job_offers).through(:friends).source(:opportunities)
|
2013-10-23 15:27:08 -04:00
|
|
|
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
|
2013-11-27 13:18:46 -05:00
|
|
|
has_one :car, through: :partner, source: :vehicle
|
2013-10-23 15:27:08 -04:00
|
|
|
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) }
|
2013-11-27 13:18:46 -05:00
|
|
|
it { should have_one(:car).through(:partner).source(:vehicle) }
|
2013-10-23 15:27:08 -04:00
|
|
|
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 13:02:42 -05:00
|
|
|
should have_one(:car).through(:partner).source(:vehicle)
|
2013-10-23 15:27:08 -04:00
|
|
|
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 11:57:31 -05:00
|
|
|
end
|
2013-10-23 15:27:08 -04:00
|
|
|
end
|
|
|
|
end
|
2012-03-09 11:57:31 -05:00
|
|
|
|
2013-10-23 15:27:08 -04: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
|
2012-11-04 22:55:51 -05:00
|
|
|
end
|
|
|
|
end
|
2013-10-23 15:27:08 -04:00
|
|
|
|
|
|
|
# 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
|
2012-11-04 22:55:51 -05:00
|
|
|
```
|
|
|
|
|
2013-10-23 15:27:08 -04:00
|
|
|
#### have_readonly_attribute
|
2012-03-09 11:57:31 -05:00
|
|
|
|
2013-10-23 15:27:08 -04:00
|
|
|
The `have_readonly_attribute` matcher tests usage of the `attr_readonly` macro.
|
2012-03-09 11:57:31 -05:00
|
|
|
|
2012-11-04 22:55:51 -05:00
|
|
|
```ruby
|
2013-10-23 15:27:08 -04:00
|
|
|
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 13:02:42 -05:00
|
|
|
|
|
|
|
# Test::Unit
|
|
|
|
class ProductTest < ActiveSupport::TestCase
|
|
|
|
should serialize(:customizations)
|
|
|
|
should serialize(:specifications).as(ProductSpecsSerializer)
|
|
|
|
should serialize(:options).as_instance_of(ProductOptionsSerializer)
|
|
|
|
end
|
2013-10-23 15:27:08 -04:00
|
|
|
```
|
|
|
|
|
|
|
|
### ActionController Matchers
|
|
|
|
|
2013-12-03 13:02:42 -05: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)*
|
2013-10-23 15:27:08 -04:00
|
|
|
|
|
|
|
#### 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 13:02:42 -05:00
|
|
|
# Test::Unit
|
2013-10-23 15:27:08 -04:00
|
|
|
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
|
2012-11-04 22:55:51 -05:00
|
|
|
end
|
|
|
|
|
2013-10-23 15:27:08 -04:00
|
|
|
# Test::Unit
|
|
|
|
class PostsControllerTest < ActionController::TestCase
|
|
|
|
context 'GET #show' do
|
|
|
|
setup { get :show }
|
|
|
|
|
|
|
|
should render_template('show')
|
|
|
|
end
|
2012-11-04 22:55:51 -05:00
|
|
|
end
|
|
|
|
```
|
2012-06-08 12:54:08 -04:00
|
|
|
|
2013-10-23 15:27:08 -04:00
|
|
|
#### 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 13:02:42 -05:00
|
|
|
get '/posts', controller: 'posts', action: 'index'
|
|
|
|
get '/posts/:id' => 'posts#show'
|
2013-10-23 15:27:08 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
# RSpec
|
|
|
|
describe 'Routing' do
|
2013-12-03 13:02:42 -05:00
|
|
|
it { should route(:get, '/posts').to(controller: 'posts', action: 'index') }
|
2013-10-23 15:27:08 -04:00
|
|
|
it { should route(:get, '/posts/1').to('posts#show', id: 1) }
|
|
|
|
end
|
|
|
|
|
|
|
|
# Test::Unit
|
|
|
|
class RoutesTest < ActionController::IntegrationTest
|
2013-12-03 13:02:42 -05:00
|
|
|
should route(:get, '/posts').to(controller: 'posts', action: 'index')
|
2013-10-23 15:27:08 -04:00
|
|
|
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 11:57:31 -05:00
|
|
|
|
|
|
|
## Credits
|
|
|
|
|
2013-10-23 15:27:08 -04:00
|
|
|
shoulda-matchers is maintained and funded by [thoughtbot][community]. Thank you
|
|
|
|
to all the [contributors][contributors].
|
2012-03-09 11:57:31 -05:00
|
|
|
|
|
|
|
## License
|
|
|
|
|
2013-10-23 15:27:08 -04:00
|
|
|
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
|