shoulda-matchers 2.4.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
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