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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +7 -4
  3. data/Appraisals +19 -7
  4. data/Gemfile.lock +1 -1
  5. data/NEWS.md +35 -0
  6. data/README.md +1204 -46
  7. data/features/step_definitions/rails_steps.rb +1 -1
  8. data/gemfiles/3.0.gemfile.lock +1 -1
  9. data/gemfiles/3.1.gemfile.lock +1 -1
  10. data/gemfiles/3.2.gemfile.lock +1 -1
  11. data/gemfiles/{4.0.gemfile → 4.0.0.gemfile} +4 -4
  12. data/gemfiles/{4.0.gemfile.lock → 4.0.0.gemfile.lock} +24 -24
  13. data/gemfiles/4.0.1.gemfile +19 -0
  14. data/gemfiles/4.0.1.gemfile.lock +161 -0
  15. data/lib/shoulda/matchers/action_controller.rb +1 -0
  16. data/lib/shoulda/matchers/action_controller/filter_param_matcher.rb +4 -2
  17. data/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb +6 -3
  18. data/lib/shoulda/matchers/action_controller/render_template_matcher.rb +6 -3
  19. data/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb +4 -2
  20. data/lib/shoulda/matchers/action_controller/rescue_from_matcher.rb +4 -2
  21. data/lib/shoulda/matchers/action_controller/respond_with_matcher.rb +4 -2
  22. data/lib/shoulda/matchers/action_controller/route_matcher.rb +13 -19
  23. data/lib/shoulda/matchers/action_controller/route_params.rb +47 -0
  24. data/lib/shoulda/matchers/action_controller/set_session_matcher.rb +4 -2
  25. data/lib/shoulda/matchers/action_controller/set_the_flash_matcher.rb +4 -2
  26. data/lib/shoulda/matchers/active_model.rb +4 -3
  27. data/lib/shoulda/matchers/active_model/allow_mass_assignment_of_matcher.rb +9 -6
  28. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +4 -2
  29. data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +6 -4
  30. data/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb +4 -1
  31. data/lib/shoulda/matchers/active_model/ensure_length_of_matcher.rb +8 -1
  32. data/lib/shoulda/matchers/active_model/have_secure_password_matcher.rb +4 -2
  33. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +59 -0
  34. data/lib/shoulda/matchers/active_model/numericality_matchers/odd_even_number_matcher.rb +51 -0
  35. data/lib/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher.rb +41 -0
  36. data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +77 -0
  37. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +14 -12
  38. data/lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb +6 -6
  39. data/lib/shoulda/matchers/active_model/validation_matcher.rb +10 -7
  40. data/lib/shoulda/matchers/active_record.rb +2 -0
  41. data/lib/shoulda/matchers/active_record/accept_nested_attributes_for_matcher.rb +4 -2
  42. data/lib/shoulda/matchers/active_record/association_matcher.rb +11 -3
  43. data/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +80 -0
  44. data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +20 -55
  45. data/lib/shoulda/matchers/active_record/association_matchers/source_matcher.rb +40 -0
  46. data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +4 -2
  47. data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +4 -2
  48. data/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +7 -4
  49. data/lib/shoulda/matchers/active_record/serialize_matcher.rb +4 -2
  50. data/lib/shoulda/matchers/integrations/test_unit.rb +3 -3
  51. data/lib/shoulda/matchers/rails_shim.rb +14 -6
  52. data/lib/shoulda/matchers/version.rb +1 -1
  53. data/spec/shoulda/matchers/action_controller/filter_param_matcher_spec.rb +1 -1
  54. data/spec/shoulda/matchers/action_controller/rescue_from_matcher_spec.rb +2 -2
  55. data/spec/shoulda/matchers/action_controller/route_matcher_spec.rb +5 -0
  56. data/spec/shoulda/matchers/action_controller/route_params_spec.rb +30 -0
  57. data/spec/shoulda/matchers/active_model/allow_mass_assignment_of_matcher_spec.rb +1 -1
  58. data/spec/shoulda/matchers/active_model/allow_value_matcher_spec.rb +1 -1
  59. data/spec/shoulda/matchers/active_model/disallow_value_matcher_spec.rb +2 -2
  60. data/spec/shoulda/matchers/active_model/ensure_inclusion_of_matcher_spec.rb +10 -0
  61. data/spec/shoulda/matchers/active_model/ensure_length_of_matcher_spec.rb +7 -0
  62. data/spec/shoulda/matchers/active_model/{comparison_matcher_spec.rb → numericality_matchers/comparison_matcher_spec.rb} +2 -2
  63. data/spec/shoulda/matchers/active_model/{odd_even_number_matcher_spec.rb → numericality_matchers/odd_even_number_matcher_spec.rb} +4 -4
  64. data/spec/shoulda/matchers/active_model/{only_integer_matcher_spec.rb → numericality_matchers/only_integer_matcher_spec.rb} +3 -3
  65. data/spec/shoulda/matchers/active_model/validate_absence_of_matcher_spec.rb +139 -0
  66. data/spec/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +7 -7
  67. data/spec/shoulda/matchers/active_model/validate_uniqueness_of_matcher_spec.rb +48 -38
  68. data/spec/shoulda/matchers/active_record/accept_nested_attributes_for_matcher_spec.rb +6 -6
  69. data/spec/shoulda/matchers/active_record/association_matcher_spec.rb +21 -8
  70. data/spec/shoulda/matchers/active_record/association_matchers/model_reflection_spec.rb +247 -0
  71. data/spec/shoulda/matchers/active_record/have_readonly_attributes_matcher_spec.rb +1 -1
  72. data/spec/shoulda/matchers/active_record/serialize_matcher_spec.rb +3 -3
  73. data/spec/spec_helper.rb +9 -15
  74. data/spec/support/active_resource_builder.rb +2 -0
  75. data/spec/support/controller_builder.rb +4 -10
  76. data/spec/support/model_builder.rb +6 -2
  77. data/spec/support/rails_versions.rb +18 -0
  78. data/spec/support/shared_examples/numerical_submatcher_spec.rb +4 -4
  79. data/spec/support/test_application.rb +97 -0
  80. metadata +30 -14
  81. data/lib/shoulda/matchers/active_model/comparison_matcher.rb +0 -57
  82. data/lib/shoulda/matchers/active_model/odd_even_number_matcher.rb +0 -47
  83. 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: 1d6e59e41d93f44812d45789d56c5b7dfee4b7fd
4
- data.tar.gz: a5d9a41130c16ac1f68735ccdf8070f0de86432a
3
+ metadata.gz: 4336f4036fb8ecbaa4ec14d3a8fbd5658164c45c
4
+ data.tar.gz: 89085b77428f146b6d4aec6d5a024ee58e34e538
5
5
  SHA512:
6
- metadata.gz: d776d5f7772c8330f3f4b3164b7e6131841e437dbe938cb453cc38a7cf63196cb92689d567bc5a51ef17f2f0a675e3b55941c851c1d2489f33ab1af8baa95387
7
- data.tar.gz: 4eccb03b31c410dc981f85e3af53743a186844d381c0d7408f50c7526d9715398733acd9062476c98a362f81146c27dd7a182d0afc4bf6e15e0bc4eebc9d2179
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 'bcrypt-ruby', '~> 3.0.0' #FIXME: This should be ~> 3.1.0 for Rails 4.0
27
- gem 'jquery-rails'
28
- gem 'sass-rails', '~> 4.0.0'
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
- # Test suite makes heavy use of attr_accessible
32
- gem 'protected_attributes'
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shoulda-matchers (2.4.0)
4
+ shoulda-matchers (2.5.0)
5
5
  activesupport (>= 3.0.0)
6
6
 
7
7
  GEM
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](https://badge.fury.io/rb/shoulda-matchers.png)](http://badge.fury.io/rb/shoulda-matchers) [![Build Status](https://secure.travis-ci.org/thoughtbot/shoulda-matchers.png?branch=master)](http://travis-ci.org/thoughtbot/shoulda-matchers)
1
+ # shoulda-matchers [![Gem Version][fury-badge]][fury] [![Build Status][travis-badge]][travis]
2
2
 
3
- [Official Documentation](http://rubydoc.info/github/thoughtbot/shoulda-matchers/master/frames)
3
+ [Official Documentation][rubydocs]
4
4
 
5
- Test::Unit- and RSpec-compatible one-liners that test common Rails functionality.
6
- These tests would otherwise be much longer, more complex, and error-prone.
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
- Refer to the [shoulda-context](https://github.com/thoughtbot/shoulda-context) gem if you want to know more
9
- about using shoulda with Test::Unit.
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
- ## ActiveRecord Matchers
47
+ #### allow_mass_assignment_of
12
48
 
13
- Matchers to test associations:
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 belong_to(:user) }
18
- it { should have_many(:tags).through(:taggings) }
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 { should have_many(:posts) }
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
- ## ActiveModel Matchers
84
+ #### allow_value / disallow_value
27
85
 
28
- Matchers to test validations and mass assignments:
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
- describe Post do
32
- it { should validate_uniqueness_of(:title) }
33
- it { should validate_uniqueness_of(:title).scoped_to(:user_id, :category_id) }
34
- it { should validate_presence_of(:body).with_message(/wtf/) }
35
- it { should validate_presence_of(:title) }
36
- it { should validate_numericality_of(:user_id) }
37
- it { should ensure_inclusion_of(:status).in_array(['draft', 'public']) }
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
- ## ActionController Matchers
315
+ #### validate_absence_of
50
316
 
51
- Matchers to test common patterns:
317
+ The `validate_absence_of` matcher tests the usage of the
318
+ `validates_absence_of` validation.
52
319
 
53
320
  ```ruby
54
- describe PostsController, "#show" do
55
- context "for a fictional user" do
56
- before do
57
- get :show, :id => 1
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
- it { should respond_with(:success) }
61
- it { should render_template(:show) }
62
- it { should_not set_the_flash }
63
- it { should rescue_from(ActiveRecord::RecordNotFound).with(:render_404) }
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
- ## Installation
829
+ #### have_readonly_attribute
69
830
 
70
- In Rails 3 and Bundler, add the following to your Gemfile:
831
+ The `have_readonly_attribute` matcher tests usage of the `attr_readonly` macro.
71
832
 
72
833
  ```ruby
73
- group :test do
74
- gem "shoulda-matchers"
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
- # `rspec-rails` needs to be in the development group so that Rails generators work.
78
- group :development, :test do
79
- gem "rspec-rails", "~> 2.12"
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
- Shoulda will automatically include matchers into the appropriate example groups.
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
- Shoulda is maintained and funded by [thoughtbot](http://thoughtbot.com/community).
88
- Thank you to all the [contributors](https://github.com/thoughtbot/shoulda-matchers/contributors).
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
- Shoulda is Copyright © 2006-2013 thoughtbot, inc.
93
- It is free software, and may be redistributed under the terms specified in the MIT-LICENSE file.
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