shoulda-matchers 2.4.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +7 -4
- data/Appraisals +19 -7
- data/Gemfile.lock +1 -1
- data/NEWS.md +35 -0
- data/README.md +1204 -46
- data/features/step_definitions/rails_steps.rb +1 -1
- data/gemfiles/3.0.gemfile.lock +1 -1
- data/gemfiles/3.1.gemfile.lock +1 -1
- data/gemfiles/3.2.gemfile.lock +1 -1
- data/gemfiles/{4.0.gemfile → 4.0.0.gemfile} +4 -4
- data/gemfiles/{4.0.gemfile.lock → 4.0.0.gemfile.lock} +24 -24
- data/gemfiles/4.0.1.gemfile +19 -0
- data/gemfiles/4.0.1.gemfile.lock +161 -0
- data/lib/shoulda/matchers/action_controller.rb +1 -0
- data/lib/shoulda/matchers/action_controller/filter_param_matcher.rb +4 -2
- data/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb +6 -3
- data/lib/shoulda/matchers/action_controller/render_template_matcher.rb +6 -3
- data/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb +4 -2
- data/lib/shoulda/matchers/action_controller/rescue_from_matcher.rb +4 -2
- data/lib/shoulda/matchers/action_controller/respond_with_matcher.rb +4 -2
- data/lib/shoulda/matchers/action_controller/route_matcher.rb +13 -19
- data/lib/shoulda/matchers/action_controller/route_params.rb +47 -0
- data/lib/shoulda/matchers/action_controller/set_session_matcher.rb +4 -2
- data/lib/shoulda/matchers/action_controller/set_the_flash_matcher.rb +4 -2
- data/lib/shoulda/matchers/active_model.rb +4 -3
- data/lib/shoulda/matchers/active_model/allow_mass_assignment_of_matcher.rb +9 -6
- data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +4 -2
- data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +6 -4
- data/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb +4 -1
- data/lib/shoulda/matchers/active_model/ensure_length_of_matcher.rb +8 -1
- data/lib/shoulda/matchers/active_model/have_secure_password_matcher.rb +4 -2
- data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +59 -0
- data/lib/shoulda/matchers/active_model/numericality_matchers/odd_even_number_matcher.rb +51 -0
- data/lib/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher.rb +41 -0
- data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +77 -0
- data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +14 -12
- data/lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb +6 -6
- data/lib/shoulda/matchers/active_model/validation_matcher.rb +10 -7
- data/lib/shoulda/matchers/active_record.rb +2 -0
- data/lib/shoulda/matchers/active_record/accept_nested_attributes_for_matcher.rb +4 -2
- data/lib/shoulda/matchers/active_record/association_matcher.rb +11 -3
- data/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +80 -0
- data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +20 -55
- data/lib/shoulda/matchers/active_record/association_matchers/source_matcher.rb +40 -0
- data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +4 -2
- data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +4 -2
- data/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +7 -4
- data/lib/shoulda/matchers/active_record/serialize_matcher.rb +4 -2
- data/lib/shoulda/matchers/integrations/test_unit.rb +3 -3
- data/lib/shoulda/matchers/rails_shim.rb +14 -6
- data/lib/shoulda/matchers/version.rb +1 -1
- data/spec/shoulda/matchers/action_controller/filter_param_matcher_spec.rb +1 -1
- data/spec/shoulda/matchers/action_controller/rescue_from_matcher_spec.rb +2 -2
- data/spec/shoulda/matchers/action_controller/route_matcher_spec.rb +5 -0
- data/spec/shoulda/matchers/action_controller/route_params_spec.rb +30 -0
- data/spec/shoulda/matchers/active_model/allow_mass_assignment_of_matcher_spec.rb +1 -1
- data/spec/shoulda/matchers/active_model/allow_value_matcher_spec.rb +1 -1
- data/spec/shoulda/matchers/active_model/disallow_value_matcher_spec.rb +2 -2
- data/spec/shoulda/matchers/active_model/ensure_inclusion_of_matcher_spec.rb +10 -0
- data/spec/shoulda/matchers/active_model/ensure_length_of_matcher_spec.rb +7 -0
- data/spec/shoulda/matchers/active_model/{comparison_matcher_spec.rb → numericality_matchers/comparison_matcher_spec.rb} +2 -2
- data/spec/shoulda/matchers/active_model/{odd_even_number_matcher_spec.rb → numericality_matchers/odd_even_number_matcher_spec.rb} +4 -4
- data/spec/shoulda/matchers/active_model/{only_integer_matcher_spec.rb → numericality_matchers/only_integer_matcher_spec.rb} +3 -3
- data/spec/shoulda/matchers/active_model/validate_absence_of_matcher_spec.rb +139 -0
- data/spec/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +7 -7
- data/spec/shoulda/matchers/active_model/validate_uniqueness_of_matcher_spec.rb +48 -38
- data/spec/shoulda/matchers/active_record/accept_nested_attributes_for_matcher_spec.rb +6 -6
- data/spec/shoulda/matchers/active_record/association_matcher_spec.rb +21 -8
- data/spec/shoulda/matchers/active_record/association_matchers/model_reflection_spec.rb +247 -0
- data/spec/shoulda/matchers/active_record/have_readonly_attributes_matcher_spec.rb +1 -1
- data/spec/shoulda/matchers/active_record/serialize_matcher_spec.rb +3 -3
- data/spec/spec_helper.rb +9 -15
- data/spec/support/active_resource_builder.rb +2 -0
- data/spec/support/controller_builder.rb +4 -10
- data/spec/support/model_builder.rb +6 -2
- data/spec/support/rails_versions.rb +18 -0
- data/spec/support/shared_examples/numerical_submatcher_spec.rb +4 -4
- data/spec/support/test_application.rb +97 -0
- metadata +30 -14
- data/lib/shoulda/matchers/active_model/comparison_matcher.rb +0 -57
- data/lib/shoulda/matchers/active_model/odd_even_number_matcher.rb +0 -47
- data/lib/shoulda/matchers/active_model/only_integer_matcher.rb +0 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4336f4036fb8ecbaa4ec14d3a8fbd5658164c45c
|
4
|
+
data.tar.gz: 89085b77428f146b6d4aec6d5a024ee58e34e538
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b5d48666690f5f108abd1925b7c157ac1e89aa73c6fa8310307f0f305811cac5539761c2ed5ebee3eaecee704740ec4adbc578614db088112b98f26cb2ca47df
|
7
|
+
data.tar.gz: dcbacdcb84ae3851a0f38f292d2b7318beab73b7b73bea4d99895a9e00d0cec027da60bd99bc2ca9405c5d311cbd97da6b5d034d0f0f5e0f26aa6b16958b029c
|
data/.travis.yml
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
script: "bundle exec rake spec cucumber"
|
2
2
|
|
3
|
-
install:
|
4
|
-
"bundle install"
|
3
|
+
install: "travis_retry bundle install"
|
5
4
|
|
6
5
|
rvm:
|
7
6
|
- 1.9.2
|
@@ -18,11 +17,15 @@ matrix:
|
|
18
17
|
- rvm: jruby-19mode
|
19
18
|
include:
|
20
19
|
- rvm: 1.9.3
|
21
|
-
gemfile: gemfiles/4.0.gemfile
|
20
|
+
gemfile: gemfiles/4.0.0.gemfile
|
21
|
+
- rvm: 1.9.3
|
22
|
+
gemfile: gemfiles/4.0.1.gemfile
|
22
23
|
- rvm: 2.0.0
|
23
24
|
gemfile: gemfiles/3.2.gemfile
|
24
25
|
- rvm: 2.0.0
|
25
|
-
gemfile: gemfiles/4.0.gemfile
|
26
|
+
gemfile: gemfiles/4.0.0.gemfile
|
27
|
+
- rvm: 2.0.0
|
28
|
+
gemfile: gemfiles/4.0.1.gemfile
|
26
29
|
- rvm: rbx-19mode
|
27
30
|
gemfile: gemfiles/3.2.gemfile
|
28
31
|
- rvm: jruby-19mode
|
data/Appraisals
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
rails_4_0 = proc do
|
2
|
+
gem 'jquery-rails'
|
3
|
+
gem 'activeresource', '4.0.0'
|
4
|
+
# Test suite makes heavy use of attr_accessible
|
5
|
+
gem 'protected_attributes'
|
6
|
+
end
|
7
|
+
|
8
|
+
#---
|
9
|
+
|
1
10
|
if RUBY_VERSION < '2.0'
|
2
11
|
appraise '3.0' do
|
3
12
|
gem 'rails', '~> 3.0.17'
|
@@ -21,13 +30,16 @@ appraise '3.2' do
|
|
21
30
|
gem 'strong_parameters'
|
22
31
|
end
|
23
32
|
|
24
|
-
appraise '4.0' do
|
33
|
+
appraise '4.0.0' do
|
34
|
+
instance_eval(&rails_4_0)
|
25
35
|
gem 'rails', '4.0.0'
|
26
|
-
gem '
|
27
|
-
gem '
|
28
|
-
|
29
|
-
gem 'activeresource', require: 'active_resource'
|
36
|
+
gem 'sass-rails', '4.0.0'
|
37
|
+
gem 'bcrypt-ruby', '~> 3.0.0'
|
38
|
+
end
|
30
39
|
|
31
|
-
|
32
|
-
|
40
|
+
appraise '4.0.1' do
|
41
|
+
instance_eval(&rails_4_0)
|
42
|
+
gem 'rails', '4.0.1'
|
43
|
+
gem 'sass-rails', '4.0.1'
|
44
|
+
gem 'bcrypt-ruby', '~> 3.1.2'
|
33
45
|
end
|
data/Gemfile.lock
CHANGED
data/NEWS.md
CHANGED
@@ -1,6 +1,41 @@
|
|
1
1
|
# HEAD
|
2
2
|
|
3
|
+
# v 2.5.0
|
4
|
+
|
5
|
+
* Fix Rails/Test::Unit integration to ensure that the test case classes we are
|
6
|
+
re-opening actually exist.
|
7
|
+
|
8
|
+
* Fix `ensure_length_of` so that it uses the right message to validate when
|
9
|
+
`is_equal_to` is specified in conjunction with a custom message.
|
10
|
+
|
11
|
+
* The `route` matcher now accepts specifying a controller/action pair as a
|
12
|
+
string instead of only a hash (e.g. `route(...).to('posts#index')` instead of
|
13
|
+
`route(...).to(controller: 'posts', action: 'index')`).
|
14
|
+
|
15
|
+
* The `ensure_inclusion_of` matcher now works with a decimal column.
|
16
|
+
|
17
|
+
* Under Rails 3, if you had an association matcher chained with the
|
18
|
+
the `order` submatcher -- e.g. `should have_many(:foos).order(:bar)` -- and
|
19
|
+
your association had an `:include` on it, using the matcher would raise an
|
20
|
+
error. This has been fixed.
|
21
|
+
|
22
|
+
* Fix `validate_uniqueness_of` so it doesn't fail if the attribute under
|
23
|
+
test has a limit of fewer than 16 characters.
|
24
|
+
|
25
|
+
* You can now test that your `has_many :through` or `has_one :through`
|
26
|
+
associations are defined with a `:source` option.
|
27
|
+
|
28
|
+
* Add new matcher `validates_absence_of`.
|
29
|
+
|
30
|
+
* Update matchers so that they use `failure_message` and
|
31
|
+
`failure_message_when_negated` to define error messages. These are new methods
|
32
|
+
in the upcoming RSpec 3 release which replace `failure_message_for_should` and
|
33
|
+
`failure_message_for_should_not`. We've kept backward compatibility so all of
|
34
|
+
your existing tests should still work -- this is just to make sure when RSpec
|
35
|
+
3 is released you don't get a bunch of warnings.
|
36
|
+
|
3
37
|
# v 2.4.0
|
38
|
+
|
4
39
|
* Fix a bug with the `validate_numericality_of` matcher that would not allow the
|
5
40
|
`with_message` option on certain submatchers.
|
6
41
|
|
data/README.md
CHANGED
@@ -1,93 +1,1251 @@
|
|
1
|
-
# shoulda-matchers [![Gem Version]
|
1
|
+
# shoulda-matchers [![Gem Version][fury-badge]][fury] [![Build Status][travis-badge]][travis]
|
2
2
|
|
3
|
-
[Official Documentation]
|
3
|
+
[Official Documentation][rubydocs]
|
4
4
|
|
5
|
-
Test::Unit- and RSpec-compatible one-liners that test
|
6
|
-
These tests would otherwise be much longer, more
|
5
|
+
shoulda-matchers provides Test::Unit- and RSpec-compatible one-liners that test
|
6
|
+
common Rails functionality. These tests would otherwise be much longer, more
|
7
|
+
complex, and error-prone.
|
7
8
|
|
8
|
-
|
9
|
-
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Simply add the following to your Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
group :test do
|
15
|
+
gem 'shoulda-matchers'
|
16
|
+
end
|
17
|
+
```
|
18
|
+
|
19
|
+
shoulda-matchers automatically includes itself into your test framework. It will
|
20
|
+
mix in the appropriate matchers for ActiveRecord, ActiveModel, and
|
21
|
+
ActionController depending on the modules that are available at runtime. For
|
22
|
+
instance, in order to use the ActiveRecord matchers, ActiveRecord must be
|
23
|
+
available beforehand.
|
24
|
+
|
25
|
+
If your application is on Rails, everything should "just work", as
|
26
|
+
shoulda-matchers will most likely be declared after Rails in your Gemfile. If
|
27
|
+
your application is on another framework such as Sinatra or Padrino, you may
|
28
|
+
have a different setup, so you will want to ensure that you are requiring
|
29
|
+
shoulda-matchers after the components of Rails you are using.
|
30
|
+
|
31
|
+
## Usage
|
32
|
+
|
33
|
+
Different matchers apply to different parts of Rails:
|
34
|
+
|
35
|
+
* [ActiveModel](#activemodel-matchers)
|
36
|
+
* [ActiveRecord](#activerecord-matchers)
|
37
|
+
* [ActionController](#actioncontroller-matchers)
|
38
|
+
|
39
|
+
### ActiveModel Matchers
|
40
|
+
|
41
|
+
*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)*
|
42
|
+
|
43
|
+
Note that all of the examples in this section are based on an ActiveRecord
|
44
|
+
model for simplicity, but these matchers will work just as well using an
|
45
|
+
ActiveModel model.
|
10
46
|
|
11
|
-
|
47
|
+
#### allow_mass_assignment_of
|
12
48
|
|
13
|
-
|
49
|
+
The `allow_mass_assignment_of` matcher tests usage of Rails 3's
|
50
|
+
`attr_accessible` and `attr_protected` macros, asserting that attributes can or
|
51
|
+
cannot be mass-assigned on a record.
|
14
52
|
|
15
53
|
```ruby
|
54
|
+
class Post < ActiveRecord::Base
|
55
|
+
attr_accessible :title
|
56
|
+
attr_accessible :published_status, as: :admin
|
57
|
+
end
|
58
|
+
|
59
|
+
class User < ActiveRecord::Base
|
60
|
+
attr_protected :encrypted_password
|
61
|
+
end
|
62
|
+
|
63
|
+
# RSpec
|
16
64
|
describe Post do
|
17
|
-
it { should
|
18
|
-
it { should
|
65
|
+
it { should allow_mass_assignment_of(:title) }
|
66
|
+
it { should allow_mass_assignment_of(:published_status).as(:admin) }
|
19
67
|
end
|
20
68
|
|
21
69
|
describe User do
|
22
|
-
it {
|
70
|
+
it { should_not allow_mass_assignment_of(:encrypted_password) }
|
71
|
+
end
|
72
|
+
|
73
|
+
# Test::Unit
|
74
|
+
class PostTest < ActiveSupport::TestCase
|
75
|
+
should allow_mass_assignment_of(:title)
|
76
|
+
should allow_mass_assignment_of(:published_status).as(:admin)
|
77
|
+
end
|
78
|
+
|
79
|
+
class UserTest < ActiveSupport::TestCase
|
80
|
+
should_not allow_mass_assignment_of(:encrypted_password)
|
23
81
|
end
|
24
82
|
```
|
25
83
|
|
26
|
-
|
84
|
+
#### allow_value / disallow_value
|
27
85
|
|
28
|
-
|
86
|
+
The `allow_value` matcher tests usage of the `validates_format_of` validation.
|
87
|
+
It asserts that an attribute can be set to one or more values, succeeding if
|
88
|
+
none of the values cause the record to be invalid.
|
29
89
|
|
30
90
|
```ruby
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
91
|
+
class UserProfile < ActiveRecord::Base
|
92
|
+
validates_format_of :website_url, with: URI.regexp
|
93
|
+
|
94
|
+
validates_format_of :birthday_as_string,
|
95
|
+
with: /^(\d+)-(\d+)-(\d+)$/,
|
96
|
+
on: :create
|
97
|
+
|
98
|
+
validates_format_of :state,
|
99
|
+
with: /^(open|closed)$/,
|
100
|
+
message: 'State must be open or closed'
|
38
101
|
end
|
39
102
|
|
103
|
+
# RSpec
|
104
|
+
describe UserProfile do
|
105
|
+
it { should allow_value('http://foo.com', 'http://bar.com/baz').for(:website_url) }
|
106
|
+
it { should_not allow_value('asdfjkl').for(:website_url) }
|
107
|
+
|
108
|
+
it { should allow_value(:birthday_as_string).on(:create) }
|
109
|
+
|
110
|
+
it do
|
111
|
+
should allow_value('open', 'closed').
|
112
|
+
for(:state).
|
113
|
+
with_message('State must be open or closed')
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Test::Unit
|
118
|
+
class UserProfileTest < ActiveSupport::TestCase
|
119
|
+
should allow_value('http://foo.com', 'http://bar.com/baz').for(:website_url)
|
120
|
+
should_not allow_value('asdfjkl').for(:website_url)
|
121
|
+
|
122
|
+
should allow_value(:birthday_as_string).on(:create)
|
123
|
+
|
124
|
+
should allow_value('open', 'closed').
|
125
|
+
for(:state).
|
126
|
+
with_message('State must be open or closed')
|
127
|
+
end
|
128
|
+
```
|
129
|
+
|
130
|
+
**PLEASE NOTE:** Using `should_not` with `allow_value` completely negates the
|
131
|
+
assertion. This means that if multiple values are given to `allow_value`, the
|
132
|
+
matcher succeeds once it sees the *first* value that will cause the record to be
|
133
|
+
invalid:
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
describe User do
|
137
|
+
# 'b' and 'c' will not be tested
|
138
|
+
it { should_not allow_value('a', 'b', 'c').for(:website_url) }
|
139
|
+
end
|
140
|
+
```
|
141
|
+
|
142
|
+
The `disallow_value` matcher is the exact opposite of `allow_value`. That is,
|
143
|
+
`should_not allow_value` can also be written `should disallow_value`, and
|
144
|
+
`should allow_value` can also be written `should_not disallow_value`. Because
|
145
|
+
of this, it carries the same caveat when multiple values are provided:
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
describe User do
|
149
|
+
# 'b' and 'c' will not be tested
|
150
|
+
it { should disallow_value('a', 'b', 'c').for(:website_url) }
|
151
|
+
end
|
152
|
+
```
|
153
|
+
|
154
|
+
#### ensure_inclusion_of
|
155
|
+
|
156
|
+
The `ensure_inclusion_of` matcher tests usage of the `validates_inclusion_of`
|
157
|
+
validation, asserting that an attribute can take a set of values and cannot
|
158
|
+
take values outside of this set.
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
class Issue < ActiveRecord::Base
|
162
|
+
validates_inclusion_of :state, in: %w(open resolved unresolved)
|
163
|
+
validates_inclusion_of :priority, in: 1..5
|
164
|
+
|
165
|
+
validates_inclusion_of :severity,
|
166
|
+
in: %w(low medium high),
|
167
|
+
message: 'Severity must be low, medium, or high'
|
168
|
+
end
|
169
|
+
|
170
|
+
# RSpec
|
171
|
+
describe Issue do
|
172
|
+
it { should ensure_inclusion_of(:state).in_array(%w(open resolved unresolved))
|
173
|
+
it { should ensure_inclusion_of(:state).in_range(1..5) }
|
174
|
+
|
175
|
+
it do
|
176
|
+
should ensure_inclusion_of(:severity).
|
177
|
+
in_array(%w(low medium high)).
|
178
|
+
with_message('Severity must be low, medium, or high')
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Test::Unit
|
183
|
+
class IssueTest < ActiveSupport::TestCase
|
184
|
+
should ensure_inclusion_of(:state).in_array(%w(open resolved unresolved))
|
185
|
+
should ensure_inclusion_of(:state).in_range(1..5)
|
186
|
+
|
187
|
+
should ensure_inclusion_of(:severity).
|
188
|
+
in_array(%w(low medium high)).
|
189
|
+
with_message('Severity must be low, medium, or high')
|
190
|
+
end
|
191
|
+
```
|
192
|
+
|
193
|
+
#### ensure_exclusion_of
|
194
|
+
|
195
|
+
The `ensure_exclusion_of` matcher tests usage of the `validates_exclusion_of`
|
196
|
+
validation, asserting that an attribute cannot take a set of values.
|
197
|
+
|
198
|
+
```ruby
|
199
|
+
class Game < ActiveRecord::Base
|
200
|
+
validates_exclusion_of :supported_os, in: ['Mac', 'Linux']
|
201
|
+
validates_exclusion_of :floors_with_enemies, in: 5..8
|
202
|
+
|
203
|
+
validates_exclusion_of :weapon,
|
204
|
+
in: ['pistol', 'paintball gun', 'stick'],
|
205
|
+
message: 'You chose a puny weapon'
|
206
|
+
end
|
207
|
+
|
208
|
+
# RSpec
|
209
|
+
describe Game do
|
210
|
+
it { should ensure_exclusion_of(:supported_os).in_array(['Mac', 'Linux']) }
|
211
|
+
it { should ensure_exclusion_of(:floors_with_enemies).in_range(5..8) }
|
212
|
+
|
213
|
+
it do
|
214
|
+
should ensure_exclusion_of(:weapon).
|
215
|
+
in_array(['pistol', 'paintball gun', 'stick']).
|
216
|
+
with_message('You chose a puny weapon')
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
# Test::Unit
|
221
|
+
class GameTest < ActiveSupport::TestCase
|
222
|
+
should ensure_exclusion_of(:supported_os).in_array(['Mac', 'Linux'])
|
223
|
+
should ensure_exclusion_of(:floors_with_enemies).in_range(5..8)
|
224
|
+
|
225
|
+
should ensure_exclusion_of(:weapon).
|
226
|
+
in_array(['pistol', 'paintball gun', 'stick']).
|
227
|
+
with_message('You chose a puny weapon')
|
228
|
+
end
|
229
|
+
```
|
230
|
+
|
231
|
+
#### ensure_length_of
|
232
|
+
|
233
|
+
The `ensure_length_of` matcher tests usage of the `validates_length_of` matcher.
|
234
|
+
|
235
|
+
```ruby
|
236
|
+
class User < ActiveRecord::Base
|
237
|
+
validates_length_of :bio, minimum: 15
|
238
|
+
validates_length_of :favorite_superhero, is: 6
|
239
|
+
validates_length_of :status_update, maximum: 140
|
240
|
+
validates_length_of :password, in: 5..30
|
241
|
+
|
242
|
+
validates_length_of :api_token,
|
243
|
+
in: 10..20,
|
244
|
+
message: 'API token must be in between 10 and 20 characters'
|
245
|
+
|
246
|
+
validates_length_of :secret_key, in: 15..100,
|
247
|
+
too_short: 'Secret key must be more than 15 characters',
|
248
|
+
too_long: 'Secret key cannot be more than 100 characters'
|
249
|
+
end
|
250
|
+
|
251
|
+
# RSpec
|
252
|
+
describe User do
|
253
|
+
it { should ensure_length_of(:bio).is_at_least(15) }
|
254
|
+
it { should ensure_length_of(:favorite_superhero).is_equal_to(6) }
|
255
|
+
it { should ensure_length_of(:status_update).is_at_most(140) }
|
256
|
+
it { should ensure_length_of(:password).is_at_least(5).is_at_most(30) }
|
257
|
+
|
258
|
+
it do
|
259
|
+
should ensure_length_of(:api_token).
|
260
|
+
is_at_least(10).
|
261
|
+
is_at_most(20).
|
262
|
+
message('Password must be in between 10 and 20 characters')
|
263
|
+
end
|
264
|
+
|
265
|
+
it do
|
266
|
+
should ensure_length_of(:secret_key).
|
267
|
+
is_at_least(15).
|
268
|
+
is_at_most(100).
|
269
|
+
with_short_message('Secret key must be more than 15 characters').
|
270
|
+
with_long_message('Secret key cannot be more than 100 characters')
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
# Test::Unit
|
275
|
+
class UserTest < ActiveSupport::TestCase
|
276
|
+
should ensure_length_of(:bio).is_at_least(15)
|
277
|
+
should ensure_length_of(:favorite_superhero).is_equal_to(6)
|
278
|
+
should ensure_length_of(:status_update).is_at_most(140)
|
279
|
+
should ensure_length_of(:password).is_at_least(5).is_at_most(30)
|
280
|
+
|
281
|
+
should ensure_length_of(:api_token).
|
282
|
+
is_at_least(15).
|
283
|
+
is_at_most(20).
|
284
|
+
message('Password must be in between 15 and 20 characters')
|
285
|
+
|
286
|
+
should ensure_length_of(:secret_key).
|
287
|
+
is_at_least(15).
|
288
|
+
is_at_most(100).
|
289
|
+
with_short_message('Secret key must be more than 15 characters').
|
290
|
+
with_long_message('Secret key cannot be more than 100 characters')
|
291
|
+
end
|
292
|
+
```
|
293
|
+
|
294
|
+
#### have_secure_password
|
295
|
+
|
296
|
+
The `have_secure_password` matcher tests usage of the `has_secure_password`
|
297
|
+
macro.
|
298
|
+
|
299
|
+
```ruby
|
300
|
+
class User < ActiveRecord::Base
|
301
|
+
has_secure_password
|
302
|
+
end
|
303
|
+
|
304
|
+
# RSpec
|
40
305
|
describe User do
|
41
|
-
it { should_not allow_value("blah").for(:email) }
|
42
|
-
it { should allow_value("a@b.com").for(:email) }
|
43
|
-
it { should ensure_inclusion_of(:age).in_range(1..100) }
|
44
|
-
it { should_not allow_mass_assignment_of(:password) }
|
45
306
|
it { should have_secure_password }
|
46
307
|
end
|
308
|
+
|
309
|
+
# Test::Unit
|
310
|
+
class UserTest < ActiveSupport::TestCase
|
311
|
+
should have_secure_password
|
312
|
+
end
|
47
313
|
```
|
48
314
|
|
49
|
-
|
315
|
+
#### validate_absence_of
|
50
316
|
|
51
|
-
|
317
|
+
The `validate_absence_of` matcher tests the usage of the
|
318
|
+
`validates_absence_of` validation.
|
52
319
|
|
53
320
|
```ruby
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
321
|
+
class Tank
|
322
|
+
include ActiveModel::Model
|
323
|
+
|
324
|
+
validates_absence_of :arms
|
325
|
+
validates_absence_of :legs,
|
326
|
+
message: "Tanks don't have legs."
|
327
|
+
end
|
328
|
+
|
329
|
+
# RSpec
|
330
|
+
describe Tank do
|
331
|
+
it { should validate_absence_of(:arms) }
|
332
|
+
|
333
|
+
it do
|
334
|
+
should validate_absence_of(:legs).
|
335
|
+
with_message("Tanks don't have legs.")
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
# Test::Unit
|
340
|
+
class TankTest < ActiveSupport::TestCase
|
341
|
+
should validate_absence_of(:arms)
|
342
|
+
|
343
|
+
should validate_absence_of(:legs).
|
344
|
+
with_message("Tanks don't have legs.")
|
345
|
+
end
|
346
|
+
```
|
347
|
+
|
348
|
+
#### validate_acceptance_of
|
349
|
+
|
350
|
+
The `validate_acceptance_of` matcher tests usage of the
|
351
|
+
`validates_acceptance_of` validation.
|
352
|
+
|
353
|
+
```ruby
|
354
|
+
class Registration < ActiveRecord::Base
|
355
|
+
validates_acceptance_of :eula
|
356
|
+
validates_acceptance_of :terms_of_service,
|
357
|
+
message: 'You must accept the terms of service'
|
358
|
+
end
|
359
|
+
|
360
|
+
# RSpec
|
361
|
+
describe Registration do
|
362
|
+
it { should validate_acceptance_of(:eula) }
|
363
|
+
|
364
|
+
it do
|
365
|
+
should validate_acceptance_of(:terms_of_service).
|
366
|
+
with_message('You must accept the terms of service')
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
# Test::Unit
|
371
|
+
class RegistrationTest < ActiveSupport::TestCase
|
372
|
+
should validate_acceptance_of(:eula)
|
373
|
+
|
374
|
+
should validate_acceptance_of(:terms_of_service).
|
375
|
+
with_message('You must accept the terms of service')
|
376
|
+
end
|
377
|
+
```
|
378
|
+
|
379
|
+
#### validate_confirmation_of
|
380
|
+
|
381
|
+
The `validate_confirmation_of` matcher tests usage of the
|
382
|
+
`validates_confirmation_of` validation.
|
383
|
+
|
384
|
+
```ruby
|
385
|
+
class User < ActiveRecord::Base
|
386
|
+
validates_confirmation_of :email
|
387
|
+
validates_confirmation_of :password, message: 'Please re-enter your password'
|
388
|
+
end
|
389
|
+
|
390
|
+
# RSpec
|
391
|
+
describe User do
|
392
|
+
it do
|
393
|
+
should validate_confirmation_of(:email)
|
394
|
+
end
|
395
|
+
|
396
|
+
it do
|
397
|
+
should validate_confirmation_of(:password).
|
398
|
+
with_message('Please re-enter your password')
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
# Test::Unit
|
403
|
+
class UserTest < ActiveSupport::TestCase
|
404
|
+
should validate_confirmation_of(:email)
|
405
|
+
|
406
|
+
should validate_confirmation_of(:password).
|
407
|
+
with_message('Please re-enter your password')
|
408
|
+
end
|
409
|
+
```
|
410
|
+
|
411
|
+
#### validate_numericality_of
|
412
|
+
|
413
|
+
The `validate_numericality_of` matcher tests usage of the
|
414
|
+
`validates_numericality_of` validation.
|
415
|
+
|
416
|
+
```ruby
|
417
|
+
class Person < ActiveRecord::Base
|
418
|
+
validates_numericality_of :gpa
|
419
|
+
validates_numericality_of :age, only_integer: true
|
420
|
+
validates_numericality_of :legal_age, greater_than: 21
|
421
|
+
validates_numericality_of :height, greater_than_or_equal_to: 55
|
422
|
+
validates_numericality_of :weight, equal_to: 150
|
423
|
+
validates_numericality_of :number_of_cars, less_than: 2
|
424
|
+
validates_numericality_of :birth_year, less_than_or_equal_to: 1987
|
425
|
+
validates_numericality_of :birth_day, odd: true
|
426
|
+
validates_numericality_of :birth_month, even: true
|
427
|
+
|
428
|
+
validates_numericality_of :number_of_dependents,
|
429
|
+
message: 'Number of dependents must be a number'
|
430
|
+
end
|
431
|
+
|
432
|
+
# RSpec
|
433
|
+
describe Person do
|
434
|
+
it { should validate_numericality_of(:gpa) }
|
435
|
+
it { should validate_numericality_of(:age).only_integer }
|
436
|
+
it { should validate_numericality_of(:legal_age).is_greater_than(21) }
|
437
|
+
it { should validate_numericality_of(:height).is_greater_than_or_equal_to(55) }
|
438
|
+
it { should validate_numericality_of(:weight).is_equal_to(150) }
|
439
|
+
it { should validate_numericality_of(:number_of_cars).is_less_than(2) }
|
440
|
+
it { should validate_numericality_of(:birth_year).is_less_than_or_equal_to(1987) }
|
441
|
+
it { should validate_numericality_of(:birth_day).odd }
|
442
|
+
it { should validate_numericality_of(:birth_month).even }
|
443
|
+
|
444
|
+
it do
|
445
|
+
should validate_numericality_of(:number_of_dependents).
|
446
|
+
with_message('Number of dependents must be a number' }
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
# Test::Unit
|
451
|
+
class PersonTest < ActiveSupport::TestCase
|
452
|
+
should validate_numericality_of(:gpa)
|
453
|
+
should validate_numericality_of(:age).only_integer
|
454
|
+
should validate_numericality_of(:legal_age).is_greater_than(21)
|
455
|
+
should validate_numericality_of(:height).is_greater_than_or_equal_to(55)
|
456
|
+
should validate_numericality_of(:weight).is_equal_to(150)
|
457
|
+
should validate_numericality_of(:number_of_cars).is_less_than(2)
|
458
|
+
should validate_numericality_of(:birth_year).is_less_than_or_equal_to(1987)
|
459
|
+
should validate_numericality_of(:birth_day).odd
|
460
|
+
should validate_numericality_of(:birth_month).even
|
461
|
+
|
462
|
+
should validate_numericality_of(:number_of_dependents).
|
463
|
+
with_message('Number of dependents must be a number')
|
464
|
+
end
|
465
|
+
```
|
466
|
+
|
467
|
+
#### validate_presence_of
|
468
|
+
|
469
|
+
The `validate_presence_of` matcher tests usage of the `validates_presence_of`
|
470
|
+
matcher.
|
471
|
+
|
472
|
+
```ruby
|
473
|
+
class Robot < ActiveRecord::Base
|
474
|
+
validates_presence_of :arms
|
475
|
+
validates_presence_of :legs, message: 'Robot has no legs'
|
476
|
+
end
|
477
|
+
|
478
|
+
# RSpec
|
479
|
+
describe Robot do
|
480
|
+
it { should validate_presence_of(:arms) }
|
481
|
+
it { should validate_presence_of(:legs).with_message('Robot has no legs') }
|
482
|
+
end
|
483
|
+
|
484
|
+
# Test::Unit
|
485
|
+
class RobotTest < ActiveSupport::TestCase
|
486
|
+
should validate_presence_of(:arms)
|
487
|
+
should validate_presence_of(:legs).with_message('Robot has no legs')
|
488
|
+
end
|
489
|
+
```
|
490
|
+
|
491
|
+
#### validate_uniqueness_of
|
492
|
+
|
493
|
+
The `validate_uniqueness_of` matcher tests usage of the
|
494
|
+
`validates_uniqueness_of` validation.
|
495
|
+
|
496
|
+
```ruby
|
497
|
+
class Post < ActiveRecord::Base
|
498
|
+
validates_uniqueness_of :permalink
|
499
|
+
validates_uniqueness_of :slug, scope: :user_id
|
500
|
+
validates_uniqueness_of :key, case_insensitive: true
|
501
|
+
validates_uniqueness_of :author_id, allow_nil: true
|
502
|
+
|
503
|
+
validates_uniqueness_of :title, message: 'Please choose another title'
|
504
|
+
end
|
505
|
+
|
506
|
+
# RSpec
|
507
|
+
describe Post do
|
508
|
+
it { should validate_uniqueness_of(:permalink) }
|
509
|
+
it { should validate_uniqueness_of(:slug).scoped_to(:journal_id) }
|
510
|
+
it { should validate_uniqueness_of(:key).case_insensitive }
|
511
|
+
it { should validate_uniqueness_of(:author_id).allow_nil }
|
512
|
+
|
513
|
+
it do
|
514
|
+
should validate_uniqueness_of(:title).
|
515
|
+
with_message('Please choose another title')
|
516
|
+
end
|
517
|
+
end
|
518
|
+
|
519
|
+
# Test::Unit
|
520
|
+
class PostTest < ActiveSupport::TestCase
|
521
|
+
should validate_uniqueness_of(:permalink)
|
522
|
+
should validate_uniqueness_of(:slug).scoped_to(:journal_id)
|
523
|
+
should validate_uniqueness_of(:key).case_insensitive
|
524
|
+
should validate_uniqueness_of(:author_id).allow_nil
|
525
|
+
|
526
|
+
should validate_uniqueness_of(:title).
|
527
|
+
with_message('Please choose another title')
|
528
|
+
end
|
529
|
+
```
|
530
|
+
|
531
|
+
**PLEASE NOTE:** This matcher works differently from other validation matchers.
|
532
|
+
Since the very concept of uniqueness depends on checking against a pre-existing
|
533
|
+
record in the database, this matcher will first use the model you're testing to
|
534
|
+
query for such a record, and if it can't find an existing one, it will create
|
535
|
+
one itself. Sometimes this step fails, especially if you have other validations
|
536
|
+
on the attribute you're testing (or, if you have database-level restrictions on
|
537
|
+
any attributes). In this case, the solution is to create a record before you use
|
538
|
+
`validate_uniqueness_of`.
|
539
|
+
|
540
|
+
For example, if you have this model:
|
541
|
+
|
542
|
+
```ruby
|
543
|
+
class Post < ActiveRecord::Base
|
544
|
+
validates_presence_of :permalink
|
545
|
+
validates_uniqueness_of :permalink
|
546
|
+
end
|
547
|
+
```
|
548
|
+
|
549
|
+
then you will need to test it like this:
|
550
|
+
|
551
|
+
```ruby
|
552
|
+
describe Post do
|
553
|
+
it do
|
554
|
+
Post.create!(title: 'This is the title')
|
555
|
+
should validate_uniqueness_of(:permalink)
|
556
|
+
end
|
557
|
+
end
|
558
|
+
```
|
559
|
+
|
560
|
+
### ActiveRecord Matchers
|
561
|
+
|
562
|
+
*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)*
|
563
|
+
|
564
|
+
#### accept_nested_attributes_for
|
565
|
+
|
566
|
+
The `accept_nested_attributes_for` matcher tests usage of the
|
567
|
+
`accepts_nested_attributes_for` macro.
|
568
|
+
|
569
|
+
```ruby
|
570
|
+
class Car < ActiveRecord::Base
|
571
|
+
accept_nested_attributes_for :doors
|
572
|
+
accept_nested_attributes_for :mirrors, allow_destroy: true
|
573
|
+
accept_nested_attributes_for :windows, limit: 3
|
574
|
+
accept_nested_attributes_for :engine, update_only: true
|
575
|
+
end
|
576
|
+
|
577
|
+
# RSpec
|
578
|
+
describe Car do
|
579
|
+
it { should accept_nested_attributes_for(:doors) }
|
580
|
+
it { should accept_nested_attributes_for(:mirrors).allow_destroy(true) }
|
581
|
+
it { should accept_nested_attributes_for(:windows).limit(3) }
|
582
|
+
it { should accept_nested_attributes_for(:engine).update_only(true) }
|
583
|
+
end
|
584
|
+
|
585
|
+
# Test::Unit (using Shoulda)
|
586
|
+
class CarTest < ActiveSupport::TestCase
|
587
|
+
should accept_nested_attributes_for(:doors)
|
588
|
+
should accept_nested_attributes_for(:mirrors).allow_destroy(true)
|
589
|
+
should accept_nested_attributes_for(:windows).limit(3)
|
590
|
+
should accept_nested_attributes_for(:engine).update_only(true)
|
591
|
+
end
|
592
|
+
```
|
593
|
+
|
594
|
+
#### belong_to
|
595
|
+
|
596
|
+
The `belong_to` matcher tests your `belongs_to` associations.
|
597
|
+
|
598
|
+
```ruby
|
599
|
+
class Person < ActiveRecord::Base
|
600
|
+
belongs_to :organization
|
601
|
+
belongs_to :family, -> { where(everyone_is_perfect: false) }
|
602
|
+
belongs_to :previous_company, -> { order('hired_on desc') }
|
603
|
+
belongs_to :ancient_city, class_name: 'City'
|
604
|
+
belongs_to :great_country, foreign_key: 'country_id'
|
605
|
+
belongs_to :mental_institution, touch: true
|
606
|
+
belongs_to :world, dependent: :destroy
|
607
|
+
end
|
608
|
+
|
609
|
+
# RSpec
|
610
|
+
describe Person do
|
611
|
+
it { should belong_to(:organization) }
|
612
|
+
it { should belong_to(:family).conditions(everyone_is_perfect: false) }
|
613
|
+
it { should belong_to(:previous_company).order('hired_on desc') }
|
614
|
+
it { should belong_to(:ancient_city).class_name('City') }
|
615
|
+
it { should belong_to(:great_country).with_foreign_key('country_id') }
|
616
|
+
it { should belong_to(:mental_institution).touch(true) }
|
617
|
+
it { should belong_to(:world).dependent(:destroy) }
|
618
|
+
end
|
619
|
+
|
620
|
+
# Test::Unit
|
621
|
+
class PersonTest < ActiveSupport::TestCase
|
622
|
+
should belong_to(:organization)
|
623
|
+
should belong_to(:family).conditions(everyone_is_perfect: false)
|
624
|
+
should belong_to(:previous_company).order('hired_on desc')
|
625
|
+
should belong_to(:ancient_city).class_name('City')
|
626
|
+
should belong_to(:great_country).with_foreign_key('country_id')
|
627
|
+
should belong_to(:mental_institution).touch(true)
|
628
|
+
should belong_to(:world).dependent(:destroy)
|
629
|
+
end
|
630
|
+
```
|
631
|
+
|
632
|
+
#### have_many
|
633
|
+
|
634
|
+
The `have_many` matcher tests your `has_many` and `has_many :through` associations.
|
635
|
+
|
636
|
+
```ruby
|
637
|
+
class Person < ActiveRecord::Base
|
638
|
+
has_many :friends
|
639
|
+
has_many :acquaintances, through: :friends
|
640
|
+
has_many :job_offers, through: :friends, source: :opportunities
|
641
|
+
has_many :coins, -> { where(condition: 'mint') }
|
642
|
+
has_many :shirts, -> { order('color') }
|
643
|
+
has_many :hopes, class_name: 'Dream'
|
644
|
+
has_many :worries, foreign_key: 'worrier_id'
|
645
|
+
has_many :distractions, counter_cache: true
|
646
|
+
has_many :ideas, validate: false
|
647
|
+
has_many :topics_of_interest, touch: true
|
648
|
+
has_many :secret_documents, dependent: :destroy
|
649
|
+
end
|
650
|
+
|
651
|
+
# RSpec
|
652
|
+
describe Person do
|
653
|
+
it { should have_many(:friends) }
|
654
|
+
it { should have_many(:acquaintances).through(:friends) }
|
655
|
+
it { should have_many(:job_offers).through(:friends).source(:opportunities) }
|
656
|
+
it { should have_many(:coins).conditions(condition: 'mint') }
|
657
|
+
it { should have_many(:shirts).order('color') }
|
658
|
+
it { should have_many(:hopes).class_name('Dream') }
|
659
|
+
it { should have_many(:worries).with_foreign_key('worrier_id') }
|
660
|
+
it { should have_many(:ideas).validate(false) }
|
661
|
+
it { should have_many(:distractions).counter_cache(true) }
|
662
|
+
it { should have_many(:topics_of_interest).touch(true) }
|
663
|
+
it { should have_many(:secret_documents).dependent(:destroy) }
|
664
|
+
end
|
665
|
+
|
666
|
+
# Test::Unit
|
667
|
+
class PersonTest < ActiveSupport::TestCase
|
668
|
+
should have_many(:friends)
|
669
|
+
should have_many(:acquaintances).through(:friends)
|
670
|
+
should have_many(:job_offers).through(:friends).source(:opportunities)
|
671
|
+
should have_many(:coins).conditions(condition: 'mint')
|
672
|
+
should have_many(:shirts).order('color')
|
673
|
+
should have_many(:hopes).class_name('Dream')
|
674
|
+
should have_many(:worries).with_foreign_key('worrier_id')
|
675
|
+
should have_many(:ideas).validate(false)
|
676
|
+
should have_many(:distractions).counter_cache(true)
|
677
|
+
should have_many(:topics_of_interest).touch(true)
|
678
|
+
should have_many(:secret_documents).dependent(:destroy)
|
679
|
+
end
|
680
|
+
```
|
681
|
+
|
682
|
+
#### have_one
|
683
|
+
|
684
|
+
The `have_one` matcher tests your `has_one` and `has_one :through` associations.
|
685
|
+
|
686
|
+
```ruby
|
687
|
+
class Person < ActiveRecord::Base
|
688
|
+
has_one :partner
|
689
|
+
has_one :life, through: :partner
|
690
|
+
has_one :car, through: :partner, source: :vehicle
|
691
|
+
has_one :pet, -> { where('weight < 80') }
|
692
|
+
has_one :focus, -> { order('priority desc') }
|
693
|
+
has_one :chance, class_name: 'Opportunity'
|
694
|
+
has_one :job, foreign_key: 'worker_id'
|
695
|
+
has_one :parking_card, validate: false
|
696
|
+
has_one :contract, dependent: :nullify
|
697
|
+
end
|
698
|
+
|
699
|
+
# RSpec
|
700
|
+
describe Person do
|
701
|
+
it { should have_one(:partner) }
|
702
|
+
it { should have_one(:life).through(:partner) }
|
703
|
+
it { should have_one(:car).through(:partner).source(:vehicle) }
|
704
|
+
it { should have_one(:pet).conditions('weight < 80') }
|
705
|
+
it { should have_one(:focus).order('priority desc') }
|
706
|
+
it { should have_one(:chance).class_name('Opportunity') }
|
707
|
+
it { should have_one(:job).with_foreign_key('worker_id') }
|
708
|
+
it { should have_one(:parking_card).validate(false) }
|
709
|
+
it { should have_one(:contract).dependent(:nullify) }
|
710
|
+
end
|
711
|
+
|
712
|
+
# Test::Unit
|
713
|
+
class PersonTest < ActiveSupport::TestCase
|
714
|
+
should have_one(:partner)
|
715
|
+
should have_one(:life).through(:partner)
|
716
|
+
should have_one(:car).through(:partner).source(:vehicle)
|
717
|
+
should have_one(:pet).conditions('weight < 80')
|
718
|
+
should have_one(:focus).order('priority desc')
|
719
|
+
should have_one(:chance).class_name('Opportunity')
|
720
|
+
should have_one(:job).with_foreign_key('worker_id')
|
721
|
+
should have_one(:parking_card).validate(false)
|
722
|
+
should have_one(:contract).dependent(:nullify)
|
723
|
+
end
|
724
|
+
```
|
725
|
+
|
726
|
+
#### have_and_belong_to_many
|
727
|
+
|
728
|
+
The `have_and_belong_to_many` matcher tests your `has_and_belongs_to_many`
|
729
|
+
associations.
|
730
|
+
|
731
|
+
```ruby
|
732
|
+
class Person < ActiveRecord::Base
|
733
|
+
has_and_belongs_to_many :awards
|
734
|
+
has_and_belongs_to_many :issues, -> { where(difficulty: 'hard') }
|
735
|
+
has_and_belongs_to_many :projects, -> { order('time_spent') }
|
736
|
+
has_and_belongs_to_many :places_visited, class_name: 'City'
|
737
|
+
has_and_belongs_to_many :interviews, validate: false
|
738
|
+
end
|
739
|
+
|
740
|
+
# RSpec
|
741
|
+
describe Person do
|
742
|
+
it { should have_and_belong_to_many(:awards) }
|
743
|
+
it { should have_and_belong_to_many(:issues).conditions(difficulty: 'hard') }
|
744
|
+
it { should have_and_belong_to_many(:projects).order('time_spent') }
|
745
|
+
it { should have_and_belong_to_many(:places_visited).class_name('City') }
|
746
|
+
it { should have_and_belong_to_many(:interviews).validate(false) }
|
747
|
+
end
|
748
|
+
|
749
|
+
# Test::Unit
|
750
|
+
class PersonTest < ActiveSupport::TestCase
|
751
|
+
should have_and_belong_to_many(:awards)
|
752
|
+
should have_and_belong_to_many(:issues).conditions(difficulty: 'hard')
|
753
|
+
should have_and_belong_to_many(:projects).order('time_spent')
|
754
|
+
should have_and_belong_to_many(:places_visited).class_name('City')
|
755
|
+
should have_and_belong_to_many(:interviews).validate(false)
|
756
|
+
end
|
757
|
+
```
|
758
|
+
|
759
|
+
#### have_db_column
|
760
|
+
|
761
|
+
The `have_db_column` matcher tests that the table that backs your model
|
762
|
+
has a specific column.
|
763
|
+
|
764
|
+
```ruby
|
765
|
+
class CreatePhones < ActiveRecord::Migration
|
766
|
+
def change
|
767
|
+
create_table :phones do |t|
|
768
|
+
t.decimal :supported_ios_version
|
769
|
+
t.string :model, null: false
|
770
|
+
t.decimal :camera_aperture, precision: 1
|
58
771
|
end
|
772
|
+
end
|
773
|
+
end
|
59
774
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
775
|
+
# RSpec
|
776
|
+
describe Phone do
|
777
|
+
it { should have_db_column(:supported_ios_version) }
|
778
|
+
it { should have_db_column(:model).with_options(null: false) }
|
779
|
+
|
780
|
+
it do
|
781
|
+
should have_db_column(:camera_aperture).
|
782
|
+
of_type(:decimal).
|
783
|
+
with_options(precision: 1)
|
784
|
+
end
|
785
|
+
end
|
786
|
+
|
787
|
+
# Test::Unit
|
788
|
+
class PhoneTest < ActiveSupport::TestCase
|
789
|
+
should have_db_column(:supported_ios_version)
|
790
|
+
should have_db_column(:model).with_options(null: false)
|
791
|
+
|
792
|
+
should have_db_column(:camera_aperture).
|
793
|
+
of_type(:decimal).
|
794
|
+
with_options(precision: 1)
|
795
|
+
end
|
796
|
+
```
|
797
|
+
|
798
|
+
#### have_db_index
|
799
|
+
|
800
|
+
The `have_db_index` matcher tests that the table that backs your model has a
|
801
|
+
index on a specific column.
|
802
|
+
|
803
|
+
```ruby
|
804
|
+
class CreateBlogs < ActiveRecord::Migration
|
805
|
+
def change
|
806
|
+
create_table :blogs do |t|
|
807
|
+
t.integer :user_id, null: false
|
808
|
+
t.string :name, null: false
|
809
|
+
end
|
810
|
+
|
811
|
+
add_index :blogs, :user_id
|
812
|
+
add_index :blogs, :name, unique: true
|
64
813
|
end
|
65
814
|
end
|
815
|
+
|
816
|
+
# RSpec
|
817
|
+
describe Blog do
|
818
|
+
it { should have_db_index(:user_id) }
|
819
|
+
it { should have_db_index(:name).unique(true) }
|
820
|
+
end
|
821
|
+
|
822
|
+
# Test::Unit
|
823
|
+
class BlogTest < ActiveSupport::TestCase
|
824
|
+
should have_db_index(:user_id)
|
825
|
+
should have_db_index(:name).unique(true)
|
826
|
+
end
|
66
827
|
```
|
67
828
|
|
68
|
-
|
829
|
+
#### have_readonly_attribute
|
69
830
|
|
70
|
-
|
831
|
+
The `have_readonly_attribute` matcher tests usage of the `attr_readonly` macro.
|
71
832
|
|
72
833
|
```ruby
|
73
|
-
|
74
|
-
|
834
|
+
class User < ActiveRecord::Base
|
835
|
+
attr_readonly :password
|
836
|
+
end
|
837
|
+
|
838
|
+
# RSpec
|
839
|
+
describe User do
|
840
|
+
it { should have_readonly_attribute(:password) }
|
841
|
+
end
|
842
|
+
|
843
|
+
# Test::Unit
|
844
|
+
class UserTest < ActiveSupport::TestCase
|
845
|
+
should have_readonly_attribute(:password)
|
846
|
+
end
|
847
|
+
```
|
848
|
+
|
849
|
+
#### serialize
|
850
|
+
|
851
|
+
The `serialize` matcher tests usage of the `serialize` macro.
|
852
|
+
|
853
|
+
```ruby
|
854
|
+
class ProductOptionsSerializer
|
855
|
+
def load(string)
|
856
|
+
# ...
|
857
|
+
end
|
858
|
+
|
859
|
+
def dump(options)
|
860
|
+
# ...
|
861
|
+
end
|
862
|
+
end
|
863
|
+
|
864
|
+
class Product < ActiveRecord::Base
|
865
|
+
serialize :customizations
|
866
|
+
serialize :specifications, ProductSpecsSerializer
|
867
|
+
serialize :options, ProductOptionsSerializer.new
|
868
|
+
end
|
869
|
+
|
870
|
+
# RSpec
|
871
|
+
describe Product do
|
872
|
+
it { should serialize(:customizations) }
|
873
|
+
it { should serialize(:specifications).as(ProductSpecsSerializer) }
|
874
|
+
it { should serialize(:options).as_instance_of(ProductOptionsSerializer) }
|
875
|
+
end
|
876
|
+
|
877
|
+
# Test::Unit
|
878
|
+
class ProductTest < ActiveSupport::TestCase
|
879
|
+
should serialize(:customizations)
|
880
|
+
should serialize(:specifications).as(ProductSpecsSerializer)
|
881
|
+
should serialize(:options).as_instance_of(ProductOptionsSerializer)
|
882
|
+
end
|
883
|
+
```
|
884
|
+
|
885
|
+
### ActionController Matchers
|
886
|
+
|
887
|
+
*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)*
|
888
|
+
|
889
|
+
#### filter_param
|
890
|
+
|
891
|
+
The `filter_param` matcher tests parameter filtering configuration.
|
892
|
+
|
893
|
+
```ruby
|
894
|
+
class MyApplication < Rails::Application
|
895
|
+
config.filter_parameters << :secret_key
|
896
|
+
end
|
897
|
+
|
898
|
+
# RSpec
|
899
|
+
describe ApplicationController do
|
900
|
+
it { should filter_param(:secret_key) }
|
901
|
+
end
|
902
|
+
|
903
|
+
# Test::Unit
|
904
|
+
class ApplicationControllerTest < ActionController::TestCase
|
905
|
+
should filter_param(:secret_key)
|
906
|
+
end
|
907
|
+
```
|
908
|
+
|
909
|
+
#### redirect_to
|
910
|
+
|
911
|
+
The `redirect_to` matcher tests that an action redirects to a certain location.
|
912
|
+
In a test suite using RSpec, it is very similar to rspec-rails's `redirect_to`
|
913
|
+
matcher. In a test suite using Test::Unit / Shoulda, it provides a more
|
914
|
+
expressive syntax over `assert_redirected_to`.
|
915
|
+
|
916
|
+
```ruby
|
917
|
+
class PostsController < ApplicationController
|
918
|
+
def show
|
919
|
+
redirect_to :index
|
920
|
+
end
|
921
|
+
end
|
922
|
+
|
923
|
+
# RSpec
|
924
|
+
describe PostsController do
|
925
|
+
describe 'GET #list' do
|
926
|
+
before { get :list }
|
927
|
+
|
928
|
+
it { should redirect_to(posts_path) }
|
929
|
+
end
|
930
|
+
end
|
931
|
+
|
932
|
+
# Test::Unit
|
933
|
+
class PostsControllerTest < ActionController::TestCase
|
934
|
+
context 'GET #list' do
|
935
|
+
setup { get :list }
|
936
|
+
|
937
|
+
should redirect_to { posts_path }
|
938
|
+
end
|
939
|
+
end
|
940
|
+
```
|
941
|
+
|
942
|
+
#### render_template
|
943
|
+
|
944
|
+
The `render_template` matcher tests that an action renders a template.
|
945
|
+
In RSpec, it is very similar to rspec-rails's `render_template` matcher.
|
946
|
+
In Test::Unit, it provides a more expressive syntax over `assert_template`.
|
947
|
+
|
948
|
+
```ruby
|
949
|
+
class PostsController < ApplicationController
|
950
|
+
def show
|
951
|
+
end
|
952
|
+
end
|
953
|
+
|
954
|
+
# RSpec
|
955
|
+
describe PostsController do
|
956
|
+
describe 'GET #show' do
|
957
|
+
before { get :show }
|
958
|
+
|
959
|
+
it { should render_template('show') }
|
960
|
+
end
|
961
|
+
end
|
962
|
+
|
963
|
+
# Test::Unit
|
964
|
+
class PostsControllerTest < ActionController::TestCase
|
965
|
+
context 'GET #show' do
|
966
|
+
setup { get :show }
|
967
|
+
|
968
|
+
should render_template('show')
|
969
|
+
end
|
970
|
+
end
|
971
|
+
```
|
972
|
+
|
973
|
+
#### render_with_layout
|
974
|
+
|
975
|
+
The `render_with_layout` matcher tests that an action is rendered with a certain
|
976
|
+
layout.
|
977
|
+
|
978
|
+
```ruby
|
979
|
+
class PostsController < ApplicationController
|
980
|
+
def show
|
981
|
+
render layout: 'posts'
|
982
|
+
end
|
983
|
+
end
|
984
|
+
|
985
|
+
# RSpec
|
986
|
+
describe PostsController do
|
987
|
+
describe 'GET #show' do
|
988
|
+
before { get :show }
|
989
|
+
|
990
|
+
it { should render_with_layout('posts') }
|
991
|
+
end
|
992
|
+
end
|
993
|
+
|
994
|
+
# Test::Unit
|
995
|
+
class PostsControllerTest < ActionController::TestCase
|
996
|
+
context 'GET #show' do
|
997
|
+
setup { get :show }
|
998
|
+
|
999
|
+
should render_with_layout('posts')
|
1000
|
+
end
|
1001
|
+
end
|
1002
|
+
```
|
1003
|
+
|
1004
|
+
#### rescue_from
|
1005
|
+
|
1006
|
+
The `rescue_from` matcher tests usage of the `rescue_from` macro.
|
1007
|
+
|
1008
|
+
```ruby
|
1009
|
+
class ApplicationController < ActionController::Base
|
1010
|
+
rescue_from ActiveRecord::RecordNotFound, with: :handle_not_found
|
1011
|
+
|
1012
|
+
private
|
1013
|
+
|
1014
|
+
def handle_not_found
|
1015
|
+
# ...
|
1016
|
+
end
|
1017
|
+
end
|
1018
|
+
|
1019
|
+
# RSpec
|
1020
|
+
describe ApplicationController do
|
1021
|
+
it do
|
1022
|
+
should rescue_from(ActiveRecord::RecordNotFound).
|
1023
|
+
with(:handle_not_found)
|
1024
|
+
end
|
1025
|
+
end
|
1026
|
+
|
1027
|
+
# Test::Unit
|
1028
|
+
class ApplicationControllerTest < ActionController::TestCase
|
1029
|
+
should rescue_from(ActiveRecord::RecordNotFound).
|
1030
|
+
with(:handle_not_found)
|
1031
|
+
end
|
1032
|
+
```
|
1033
|
+
|
1034
|
+
#### respond_with
|
1035
|
+
|
1036
|
+
The `respond_with` matcher tests that an action responds with a certain status
|
1037
|
+
code.
|
1038
|
+
|
1039
|
+
```ruby
|
1040
|
+
class PostsController < ApplicationController
|
1041
|
+
def index
|
1042
|
+
render status: 403
|
1043
|
+
end
|
1044
|
+
|
1045
|
+
def show
|
1046
|
+
render status: :locked
|
1047
|
+
end
|
1048
|
+
|
1049
|
+
def destroy
|
1050
|
+
render status: 508
|
1051
|
+
end
|
1052
|
+
end
|
1053
|
+
|
1054
|
+
# RSpec
|
1055
|
+
describe PostsController do
|
1056
|
+
describe 'GET #index' do
|
1057
|
+
before { get :index }
|
1058
|
+
|
1059
|
+
it { should respond_with(403) }
|
1060
|
+
end
|
1061
|
+
|
1062
|
+
describe 'GET #show' do
|
1063
|
+
before { get :show }
|
1064
|
+
|
1065
|
+
it { should respond_with(:locked) }
|
1066
|
+
end
|
1067
|
+
|
1068
|
+
describe 'DELETE #destroy' do
|
1069
|
+
before { delete :destroy }
|
1070
|
+
|
1071
|
+
it { should respond_with(500..600) }
|
1072
|
+
end
|
1073
|
+
end
|
1074
|
+
|
1075
|
+
# Test::Unit
|
1076
|
+
class PostsControllerTest < ActionController::TestCase
|
1077
|
+
context 'GET #index' do
|
1078
|
+
setup { get :index }
|
1079
|
+
|
1080
|
+
should respond_with(403)
|
1081
|
+
end
|
1082
|
+
|
1083
|
+
context 'GET #show' do
|
1084
|
+
setup { get :show }
|
1085
|
+
|
1086
|
+
should respond_with(:locked)
|
1087
|
+
end
|
1088
|
+
|
1089
|
+
context 'DELETE #destroy' do
|
1090
|
+
setup { delete :destroy }
|
1091
|
+
|
1092
|
+
should respond_with(500..600)
|
1093
|
+
end
|
1094
|
+
end
|
1095
|
+
```
|
1096
|
+
|
1097
|
+
#### route
|
1098
|
+
|
1099
|
+
The `route` matcher tests that a route resolves to a controller, action, and
|
1100
|
+
params; and that the controller, action, and params generates the same route. For
|
1101
|
+
an RSpec suite, this is like using a combination of `route_to` and
|
1102
|
+
`be_routable`. For a Test::Unit suite, it provides a more expressive syntax
|
1103
|
+
over `assert_routing`.
|
1104
|
+
|
1105
|
+
```ruby
|
1106
|
+
My::Application.routes.draw do
|
1107
|
+
get '/posts', controller: 'posts', action: 'index'
|
1108
|
+
get '/posts/:id' => 'posts#show'
|
1109
|
+
end
|
1110
|
+
|
1111
|
+
# RSpec
|
1112
|
+
describe 'Routing' do
|
1113
|
+
it { should route(:get, '/posts').to(controller: 'posts', action: 'index') }
|
1114
|
+
it { should route(:get, '/posts/1').to('posts#show', id: 1) }
|
1115
|
+
end
|
1116
|
+
|
1117
|
+
# Test::Unit
|
1118
|
+
class RoutesTest < ActionController::IntegrationTest
|
1119
|
+
should route(:get, '/posts').to(controller: 'posts', action: 'index')
|
1120
|
+
should route(:get, '/posts/1').to('posts#show', id: 1)
|
1121
|
+
end
|
1122
|
+
```
|
1123
|
+
|
1124
|
+
#### set_session
|
1125
|
+
|
1126
|
+
The `set_session` matcher asserts that a key in the `session` hash has been set
|
1127
|
+
to a certain value.
|
1128
|
+
|
1129
|
+
```ruby
|
1130
|
+
class PostsController < ApplicationController
|
1131
|
+
def show
|
1132
|
+
session[:foo] = 'bar'
|
1133
|
+
end
|
1134
|
+
end
|
1135
|
+
|
1136
|
+
# RSpec
|
1137
|
+
describe PostsController do
|
1138
|
+
describe 'GET #show' do
|
1139
|
+
before { get :show }
|
1140
|
+
|
1141
|
+
it { should set_session(:foo).to('bar') }
|
1142
|
+
it { should_not set_session(:baz) }
|
1143
|
+
end
|
1144
|
+
end
|
1145
|
+
|
1146
|
+
# Test::Unit
|
1147
|
+
class PostsControllerTest < ActionController::TestCase
|
1148
|
+
context 'GET #show' do
|
1149
|
+
setup { get :show }
|
1150
|
+
|
1151
|
+
should set_session(:foo).to('bar')
|
1152
|
+
should_not set_session(:baz)
|
1153
|
+
end
|
1154
|
+
end
|
1155
|
+
```
|
1156
|
+
|
1157
|
+
#### set_the_flash
|
1158
|
+
|
1159
|
+
The `set_the_flash` matcher asserts that a key in the `flash` hash is set to a
|
1160
|
+
certain value.
|
1161
|
+
|
1162
|
+
```ruby
|
1163
|
+
class PostsController < ApplicationController
|
1164
|
+
def index
|
1165
|
+
flash[:foo] = 'A candy bar'
|
1166
|
+
end
|
1167
|
+
|
1168
|
+
def show
|
1169
|
+
flash.now[:foo] = 'bar'
|
1170
|
+
end
|
1171
|
+
|
1172
|
+
def destroy
|
1173
|
+
end
|
1174
|
+
end
|
1175
|
+
|
1176
|
+
# RSpec
|
1177
|
+
describe PostsController do
|
1178
|
+
describe 'GET #index' do
|
1179
|
+
before { get :index }
|
1180
|
+
|
1181
|
+
it { should set_the_flash.to('bar') }
|
1182
|
+
it { should set_the_flash.to(/bar/) }
|
1183
|
+
it { should set_the_flash[:foo].to('bar') }
|
1184
|
+
it { should_not set_the_flash[:baz] }
|
1185
|
+
end
|
1186
|
+
|
1187
|
+
describe 'GET #show' do
|
1188
|
+
before { get :show }
|
1189
|
+
|
1190
|
+
it { should set_the_flash.now }
|
1191
|
+
it { should set_the_flash[:foo].now }
|
1192
|
+
it { should set_the_flash[:foo].to('bar').now }
|
1193
|
+
end
|
1194
|
+
|
1195
|
+
describe 'DELETE #destroy' do
|
1196
|
+
before { delete :destroy }
|
1197
|
+
|
1198
|
+
it { should_not set_the_flash }
|
1199
|
+
end
|
75
1200
|
end
|
76
1201
|
|
77
|
-
#
|
78
|
-
|
79
|
-
|
1202
|
+
# Test::Unit
|
1203
|
+
class PostsControllerTest < ActionController::TestCase
|
1204
|
+
context 'GET #index' do
|
1205
|
+
setup { get :index }
|
1206
|
+
|
1207
|
+
should set_the_flash.to('bar')
|
1208
|
+
should set_the_flash.to(/bar/)
|
1209
|
+
should set_the_flash[:foo].to('bar')
|
1210
|
+
should_not set_the_flash[:baz]
|
1211
|
+
end
|
1212
|
+
|
1213
|
+
context 'GET #show' do
|
1214
|
+
setup { get :show }
|
1215
|
+
|
1216
|
+
should set_the_flash.now
|
1217
|
+
should set_the_flash[:foo].now
|
1218
|
+
should set_the_flash[:foo].to('bar').now
|
1219
|
+
end
|
1220
|
+
|
1221
|
+
context 'DELETE #destroy' do
|
1222
|
+
setup { delete :destroy }
|
1223
|
+
|
1224
|
+
should_not set_the_flash
|
1225
|
+
end
|
80
1226
|
end
|
81
1227
|
```
|
82
1228
|
|
83
|
-
|
1229
|
+
## Versioning
|
1230
|
+
|
1231
|
+
shoulda-matchers follows Semantic Versioning 2.0 as defined at
|
1232
|
+
<http://semver.org>.
|
84
1233
|
|
85
1234
|
## Credits
|
86
1235
|
|
87
|
-
|
88
|
-
|
1236
|
+
shoulda-matchers is maintained and funded by [thoughtbot][community]. Thank you
|
1237
|
+
to all the [contributors][contributors].
|
89
1238
|
|
90
1239
|
## License
|
91
1240
|
|
92
|
-
|
93
|
-
|
1241
|
+
shoulda-matchers is copyright © 2006-2013 thoughtbot, inc. It is free software,
|
1242
|
+
and may be redistributed under the terms specified in the
|
1243
|
+
[MIT-LICENSE](MIT-LICENSE) file.
|
1244
|
+
|
1245
|
+
[fury-badge]: https://badge.fury.io/rb/shoulda-matchers.png
|
1246
|
+
[fury]: http://badge.fury.io/rb/shoulda-matchers
|
1247
|
+
[travis-badge]: https://secure.travis-ci.org/thoughtbot/shoulda-matchers.png?branch=master
|
1248
|
+
[travis]: http://travis-ci.org/thoughtbot/shoulda-matchers
|
1249
|
+
[rubydocs]: http://rubydoc.info/github/thoughtbot/shoulda-matchers/master/frames
|
1250
|
+
[community]: http://thoughtbot.com/community
|
1251
|
+
[contributors]: https://github.com/thoughtbot/shoulda-matchers/contributors
|