rspec-rails 3.0.2 → 3.1.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 (42) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/Changelog.md +17 -0
  5. data/README.md +7 -7
  6. data/lib/generators/rspec.rb +10 -6
  7. data/lib/generators/rspec/controller/templates/controller_spec.rb +1 -1
  8. data/lib/generators/rspec/install/install_generator.rb +19 -2
  9. data/lib/generators/rspec/install/templates/spec/rails_helper.rb +13 -4
  10. data/lib/generators/rspec/integration/templates/request_spec.rb +1 -1
  11. data/lib/generators/rspec/job/job_generator.rb +12 -0
  12. data/lib/generators/rspec/job/templates/job_spec.rb.erb +7 -0
  13. data/lib/generators/rspec/model/model_generator.rb +18 -5
  14. data/lib/generators/rspec/scaffold/scaffold_generator.rb +107 -100
  15. data/lib/rspec-rails.rb +1 -1
  16. data/lib/rspec/rails.rb +1 -0
  17. data/lib/rspec/rails/adapters.rb +9 -7
  18. data/lib/rspec/rails/configuration.rb +2 -3
  19. data/lib/rspec/rails/example/controller_example_group.rb +172 -149
  20. data/lib/rspec/rails/example/feature_example_group.rb +25 -23
  21. data/lib/rspec/rails/example/helper_example_group.rb +27 -25
  22. data/lib/rspec/rails/example/mailer_example_group.rb +26 -22
  23. data/lib/rspec/rails/example/model_example_group.rb +8 -6
  24. data/lib/rspec/rails/example/request_example_group.rb +19 -17
  25. data/lib/rspec/rails/example/routing_example_group.rb +40 -38
  26. data/lib/rspec/rails/example/view_example_group.rb +140 -137
  27. data/lib/rspec/rails/extensions/active_record/proxy.rb +0 -1
  28. data/lib/rspec/rails/feature_check.rb +35 -0
  29. data/lib/rspec/rails/fixture_support.rb +1 -1
  30. data/lib/rspec/rails/matchers.rb +5 -2
  31. data/lib/rspec/rails/matchers/be_a_new.rb +68 -64
  32. data/lib/rspec/rails/matchers/be_new_record.rb +24 -20
  33. data/lib/rspec/rails/matchers/be_valid.rb +38 -34
  34. data/lib/rspec/rails/matchers/have_http_status.rb +340 -334
  35. data/lib/rspec/rails/matchers/have_rendered.rb +35 -32
  36. data/lib/rspec/rails/matchers/redirect_to.rb +30 -27
  37. data/lib/rspec/rails/matchers/routing_matchers.rb +103 -101
  38. data/lib/rspec/rails/version.rb +1 -1
  39. data/lib/rspec/rails/view_assigns.rb +1 -2
  40. data/lib/rspec/rails/view_rendering.rb +6 -8
  41. metadata +16 -13
  42. metadata.gz.sig +3 -2
@@ -14,4 +14,3 @@ RSpec.configure do |rspec|
14
14
  end
15
15
  end
16
16
  end
17
-
@@ -0,0 +1,35 @@
1
+ # @api private
2
+ module RSpec
3
+ module Rails
4
+ # Disable some cops until https://github.com/bbatsov/rubocop/issues/1310
5
+ # rubocop:disable Style/IndentationConsistency
6
+ module FeatureCheck
7
+ # rubocop:disable Style/IndentationWidth
8
+ module_function
9
+ # rubocop:enable Style/IndentationWidth
10
+
11
+ def can_check_pending_migrations?
12
+ has_active_record_migration? &&
13
+ ::ActiveRecord::Migration.respond_to?(:check_pending!)
14
+ end
15
+
16
+ def can_maintain_test_schema?
17
+ has_active_record_migration? &&
18
+ ::ActiveRecord::Migration.respond_to?(:maintain_test_schema!)
19
+ end
20
+
21
+ def has_active_job?
22
+ defined?(::ActiveJob)
23
+ end
24
+
25
+ def has_active_record?
26
+ defined?(::ActiveRecord)
27
+ end
28
+
29
+ def has_active_record_migration?
30
+ has_active_record? && defined?(::ActiveRecord::Migration)
31
+ end
32
+ end
33
+ # rubocop:enable Style/IndentationConsistency
34
+ end
35
+ end
@@ -10,7 +10,7 @@ module RSpec
10
10
  include ActiveRecord::TestFixtures
11
11
 
12
12
  included do
13
- # TODO (DC 2011-06-25) this is necessary because fixture_file_upload
13
+ # TODO: (DC 2011-06-25) this is necessary because fixture_file_upload
14
14
  # accesses fixture_path directly on ActiveSupport::TestCase. This is
15
15
  # fixed in rails by https://github.com/rails/rails/pull/1861, which
16
16
  # should be part of the 3.1 release, at which point we can include
@@ -1,8 +1,11 @@
1
1
  require 'rspec/core/warnings'
2
2
  require 'rspec/expectations'
3
3
 
4
- module RSpec::Rails
5
- module Matchers
4
+ module RSpec
5
+ module Rails
6
+ # Container module for Rails specific matchers.
7
+ module Matchers
8
+ end
6
9
  end
7
10
  end
8
11
 
@@ -1,77 +1,81 @@
1
- module RSpec::Rails::Matchers
2
- # @api private
3
- #
4
- # Matcher class for `be_a_new`. Should not be instantiated directly.
5
- #
6
- # @see RSpec::Rails::Matchers#be_a_new
7
- class BeANew < RSpec::Matchers::BuiltIn::BaseMatcher
8
- # @private
9
- def initialize(expected)
10
- @expected = expected
11
- end
12
-
13
- # @private
14
- def matches?(actual)
15
- @actual = actual
16
- actual.is_a?(expected) && actual.new_record? && attributes_match?(actual)
17
- end
1
+ module RSpec
2
+ module Rails
3
+ module Matchers
4
+ # @api private
5
+ #
6
+ # Matcher class for `be_a_new`. Should not be instantiated directly.
7
+ #
8
+ # @see RSpec::Rails::Matchers#be_a_new
9
+ class BeANew < RSpec::Matchers::BuiltIn::BaseMatcher
10
+ # @private
11
+ def initialize(expected)
12
+ @expected = expected
13
+ end
18
14
 
19
- # @api public
20
- # @see RSpec::Rails::Matchers#be_a_new
21
- def with(expected_attributes)
22
- attributes.merge!(expected_attributes)
23
- self
24
- end
15
+ # @private
16
+ def matches?(actual)
17
+ @actual = actual
18
+ actual.is_a?(expected) && actual.new_record? && attributes_match?(actual)
19
+ end
25
20
 
26
- # @private
27
- def failure_message
28
- [].tap do |message|
29
- unless actual.is_a?(expected) && actual.new_record?
30
- message << "expected #{actual.inspect} to be a new #{expected.inspect}"
21
+ # @api public
22
+ # @see RSpec::Rails::Matchers#be_a_new
23
+ def with(expected_attributes)
24
+ attributes.merge!(expected_attributes)
25
+ self
31
26
  end
32
- unless attributes_match?(actual)
33
- if unmatched_attributes.size > 1
34
- message << "attributes #{unmatched_attributes.inspect} were not set on #{actual.inspect}"
35
- else
36
- message << "attribute #{unmatched_attributes.inspect} was not set on #{actual.inspect}"
37
- end
27
+
28
+ # @private
29
+ def failure_message
30
+ [].tap do |message|
31
+ unless actual.is_a?(expected) && actual.new_record?
32
+ message << "expected #{actual.inspect} to be a new #{expected.inspect}"
33
+ end
34
+ unless attributes_match?(actual)
35
+ if unmatched_attributes.size > 1
36
+ message << "attributes #{unmatched_attributes.inspect} were not set on #{actual.inspect}"
37
+ else
38
+ message << "attribute #{unmatched_attributes.inspect} was not set on #{actual.inspect}"
39
+ end
40
+ end
41
+ end.join(' and ')
38
42
  end
39
- end.join(' and ')
40
- end
41
43
 
42
- private
44
+ private
43
45
 
44
- def attributes
45
- @attributes ||= {}
46
- end
46
+ def attributes
47
+ @attributes ||= {}
48
+ end
47
49
 
48
- def attributes_match?(actual)
49
- attributes.stringify_keys.all? do |key, value|
50
- actual.attributes[key].eql?(value)
50
+ def attributes_match?(actual)
51
+ attributes.stringify_keys.all? do |key, value|
52
+ actual.attributes[key].eql?(value)
53
+ end
54
+ end
55
+
56
+ def unmatched_attributes
57
+ attributes.stringify_keys.reject do |key, value|
58
+ actual.attributes[key].eql?(value)
59
+ end
60
+ end
51
61
  end
52
- end
53
62
 
54
- def unmatched_attributes
55
- attributes.stringify_keys.reject do |key, value|
56
- actual.attributes[key].eql?(value)
63
+ # Passes if actual is an instance of `model_class` and returns `false` for
64
+ # `persisted?`. Typically used to specify instance variables assigned to
65
+ # views by controller actions
66
+ #
67
+ # Use the `with` method to specify the specific attributes to match on the
68
+ # new record.
69
+ #
70
+ # @example
71
+ # get :new
72
+ # assigns(:thing).should be_a_new(Thing)
73
+ #
74
+ # post :create, :thing => { :name => "Illegal Value" }
75
+ # assigns(:thing).should be_a_new(Thing).with(:name => nil)
76
+ def be_a_new(model_class)
77
+ BeANew.new(model_class)
57
78
  end
58
79
  end
59
80
  end
60
-
61
- # Passes if actual is an instance of `model_class` and returns `false` for
62
- # `persisted?`. Typically used to specify instance variables assigned to
63
- # views by controller actions
64
- #
65
- # Use the `with` method to specify the specific attributes to match on the
66
- # new record.
67
- #
68
- # @example
69
- # get :new
70
- # assigns(:thing).should be_a_new(Thing)
71
- #
72
- # post :create, :thing => { :name => "Illegal Value" }
73
- # assigns(:thing).should be_a_new(Thing).with(:name => nil)
74
- def be_a_new(model_class)
75
- BeANew.new(model_class)
76
- end
77
81
  end
@@ -1,25 +1,29 @@
1
- module RSpec::Rails::Matchers
2
- # @private
3
- class BeANewRecord < RSpec::Matchers::BuiltIn::BaseMatcher
4
- def matches?(actual)
5
- !actual.persisted?
6
- end
1
+ module RSpec
2
+ module Rails
3
+ module Matchers
4
+ # @private
5
+ class BeANewRecord < RSpec::Matchers::BuiltIn::BaseMatcher
6
+ def matches?(actual)
7
+ !actual.persisted?
8
+ end
7
9
 
8
- def failure_message
9
- "expected #{actual.inspect} to be a new record, but was persisted"
10
- end
10
+ def failure_message
11
+ "expected #{actual.inspect} to be a new record, but was persisted"
12
+ end
11
13
 
12
- def failure_message_when_negated
13
- "expected #{actual.inspect} to be persisted, but was a new record"
14
- end
15
- end
14
+ def failure_message_when_negated
15
+ "expected #{actual.inspect} to be persisted, but was a new record"
16
+ end
17
+ end
16
18
 
17
- # Passes if actual returns `false` for `persisted?`.
18
- #
19
- # @example
20
- # get :new
21
- # expect(assigns(:thing)).to be_new_record
22
- def be_new_record
23
- BeANewRecord.new
19
+ # Passes if actual returns `false` for `persisted?`.
20
+ #
21
+ # @example
22
+ # get :new
23
+ # expect(assigns(:thing)).to be_new_record
24
+ def be_new_record
25
+ BeANewRecord.new
26
+ end
27
+ end
24
28
  end
25
29
  end
@@ -1,44 +1,48 @@
1
- module RSpec::Rails::Matchers
2
- # @private
3
- class BeValid < RSpec::Matchers::BuiltIn::Be
4
- def initialize(*args)
5
- @args = args
6
- end
1
+ module RSpec
2
+ module Rails
3
+ module Matchers
4
+ # @private
5
+ class BeValid < RSpec::Matchers::BuiltIn::Be
6
+ def initialize(*args)
7
+ @args = args
8
+ end
7
9
 
8
- def matches?(actual)
9
- @actual = actual
10
- actual.valid?(*@args)
11
- end
10
+ def matches?(actual)
11
+ @actual = actual
12
+ actual.valid?(*@args)
13
+ end
14
+
15
+ def failure_message
16
+ message = "expected #{actual.inspect} to be valid"
17
+
18
+ if actual.respond_to?(:errors)
19
+ errors = if actual.errors.respond_to?(:full_messages)
20
+ actual.errors.full_messages
21
+ else
22
+ actual.errors
23
+ end
12
24
 
13
- def failure_message
14
- message = "expected #{actual.inspect} to be valid"
25
+ message << ", but got errors: #{errors.map(&:to_s).join(', ')}"
26
+ end
15
27
 
16
- if actual.respond_to?(:errors)
17
- errors = if actual.errors.respond_to?(:full_messages)
18
- actual.errors.full_messages
19
- else
20
- actual.errors
28
+ message
21
29
  end
22
30
 
23
- message << ", but got errors: #{errors.map(&:to_s).join(', ')}"
31
+ def failure_message_when_negated
32
+ "expected #{actual.inspect} not to be valid"
33
+ end
24
34
  end
25
35
 
26
- message
27
- end
28
-
29
- def failure_message_when_negated
30
- "expected #{actual.inspect} not to be valid"
36
+ # Passes if the given model instance's `valid?` method is true, meaning
37
+ # all of the `ActiveModel::Validations` passed and no errors exist. If a
38
+ # message is not given, a default message is shown listing each error.
39
+ #
40
+ # @example
41
+ # thing = Thing.new
42
+ # expect(thing).to be_valid
43
+ def be_valid(*args)
44
+ BeValid.new(*args)
45
+ end
31
46
  end
32
47
  end
33
-
34
- # Passes if the given model instance's `valid?` method is true, meaning all
35
- # of the `ActiveModel::Validations` passed and no errors exist. If a message
36
- # is not given, a default message is shown listing each error.
37
- #
38
- # @example
39
- # thing = Thing.new
40
- # expect(thing).to be_valid
41
- def be_valid(*args)
42
- BeValid.new(*args)
43
- end
44
48
  end
@@ -4,352 +4,358 @@
4
4
  #
5
5
  # Thank you to all the Rails devs who did the heavy lifting on this!
6
6
 
7
- module RSpec::Rails::Matchers
8
- # Namespace for various implementations of `have_http_status`.
9
- #
10
- # @api private
11
- module HaveHttpStatus
12
- # Instantiates an instance of the proper matcher based on the provided
13
- # `target`.
14
- #
15
- # @param target [Object] expected http status or code
16
- # @return response matcher instance
17
- def self.matcher_for_status(target)
18
- if GenericStatus.valid_statuses.include?(target)
19
- GenericStatus.new(target)
20
- elsif Symbol === target
21
- SymbolicStatus.new(target)
22
- else
23
- NumericCode.new(target)
24
- end
25
- end
26
-
27
- # @api private
28
- # Conversion function to coerce the provided object into an
29
- # `ActionDispatch::TestResponse`.
30
- #
31
- # @param obj [Object] object to convert to a response
32
- # @return [ActionDispatch::TestResponse]
33
- def as_test_response(obj)
34
- if ::ActionDispatch::Response === obj
35
- ::ActionDispatch::TestResponse.from_response(obj)
36
- elsif ::ActionDispatch::TestResponse === obj
37
- obj
38
- elsif obj.respond_to?(:status_code) && obj.respond_to?(:response_headers)
39
- # Acts As Capybara Session
40
- # Hack to support `Capybara::Session` without having to load Capybara or
41
- # catch `NameError`s for the undefined constants
42
- ::ActionDispatch::TestResponse.new.tap{ |resp|
43
- resp.status = obj.status_code
44
- resp.headers = obj.response_headers
45
- resp.body = obj.body
46
- }
47
- else
48
- raise TypeError, "Invalid response type: #{obj}"
49
- end
50
- end
51
- module_function :as_test_response
52
-
53
- # @return [String, nil] a formatted failure message if `@invalid_response`
54
- # is present, `nil` otherwise
55
- def invalid_response_type_message
56
- if @invalid_response
57
- "expected a response object, but an instance of " +
58
- "#{@invalid_response.class} was received"
59
- end
60
- end
61
-
62
- # @api private
63
- # Provides an implementation for `have_http_status` matching against
64
- # numeric http status codes.
65
- #
66
- # Not intended to be instantiated directly.
67
- #
68
- # @example
69
- # expect(response).to have_http_status(404)
70
- #
71
- # @see RSpec::Rails::Matchers.have_http_status
72
- class NumericCode < RSpec::Matchers::BuiltIn::BaseMatcher
73
- include HaveHttpStatus
74
-
75
- def initialize(code)
76
- @expected = code.to_i
77
- @actual = nil
78
- @invalid_response = nil
79
- end
80
-
81
- # @param [Object] response object providing an http code to match
82
- # @return [Boolean] `true` if the numeric code matched the `response` code
83
- def matches?(response)
84
- test_response = as_test_response(response)
85
- @actual = test_response.response_code
86
- expected == @actual
87
- rescue TypeError => _ignored
88
- @invalid_response = response
89
- false
90
- end
91
-
92
- # @return [String]
93
- def description
94
- "respond with numeric status code #{expected}"
95
- end
96
-
97
- # @return [String] explaining why the match failed
98
- def failure_message
99
- invalid_response_type_message ||
100
- "expected the response to have status code #{expected.inspect}" +
101
- " but it was #{actual.inspect}"
102
- end
103
-
104
- # @return [String] explaining why the match failed
105
- def failure_message_when_negated
106
- invalid_response_type_message ||
107
- "expected the response not to have status code #{expected.inspect}" +
108
- " but it did"
109
- end
110
- end
111
-
112
- # @api private
113
- # Provides an implementation for `have_http_status` matching against
114
- # Rack symbol http status codes.
115
- #
116
- # Not intended to be instantiated directly.
117
- #
118
- # @example
119
- # expect(response).to have_http_status(:created)
120
- #
121
- # @see RSpec::Rails::Matchers.have_http_status
122
- # @see https://github.com/rack/rack/blob/master/lib/rack/utils.rb `Rack::Utils::SYMBOL_TO_STATUS_CODE`
123
- class SymbolicStatus < RSpec::Matchers::BuiltIn::BaseMatcher
124
- include HaveHttpStatus
125
-
126
- def initialize(status)
127
- @expected_status = status
128
- @actual = nil
129
- @invalid_response = nil
130
- unless set_expected_code!
131
- raise ArgumentError, "Invalid HTTP status: #{status.inspect}"
7
+ module RSpec
8
+ module Rails
9
+ module Matchers
10
+ # Namespace for various implementations of `have_http_status`.
11
+ #
12
+ # @api private
13
+ module HaveHttpStatus
14
+ # Instantiates an instance of the proper matcher based on the provided
15
+ # `target`.
16
+ #
17
+ # @param target [Object] expected http status or code
18
+ # @return response matcher instance
19
+ def self.matcher_for_status(target)
20
+ if GenericStatus.valid_statuses.include?(target)
21
+ GenericStatus.new(target)
22
+ elsif Symbol === target
23
+ SymbolicStatus.new(target)
24
+ else
25
+ NumericCode.new(target)
26
+ end
132
27
  end
133
- end
134
-
135
- # @param [Object] response object providing an http code to match
136
- # @return [Boolean] `true` if Rack's associated numeric HTTP code matched
137
- # the `response` code
138
- def matches?(response)
139
- test_response = as_test_response(response)
140
- @actual = test_response.response_code
141
- expected == @actual
142
- rescue TypeError => _ignored
143
- @invalid_response = response
144
- false
145
- end
146
-
147
- # @return [String]
148
- def description
149
- "respond with status code #{pp_expected}"
150
- end
151
-
152
- # @return [String] explaining why the match failed
153
- def failure_message
154
- invalid_response_type_message ||
155
- "expected the response to have status code #{pp_expected} but it" +
156
- " was #{pp_actual}"
157
- end
158
-
159
- # @return [String] explaining why the match failed
160
- def failure_message_when_negated
161
- invalid_response_type_message ||
162
- "expected the response not to have status code #{pp_expected} " +
163
- "but it did"
164
- end
165
-
166
- # The initialized expected status symbol
167
- attr_reader :expected_status
168
- private :expected_status
169
28
 
170
- private
171
-
172
- # @return [Symbol] representing the actual http numeric code
173
- def actual_status
174
- if actual
175
- @actual_status ||= compute_status_from(actual)
29
+ # @api private
30
+ # Conversion function to coerce the provided object into an
31
+ # `ActionDispatch::TestResponse`.
32
+ #
33
+ # @param obj [Object] object to convert to a response
34
+ # @return [ActionDispatch::TestResponse]
35
+ def as_test_response(obj)
36
+ if ::ActionDispatch::Response === obj
37
+ ::ActionDispatch::TestResponse.from_response(obj)
38
+ elsif ::ActionDispatch::TestResponse === obj
39
+ obj
40
+ elsif obj.respond_to?(:status_code) && obj.respond_to?(:response_headers)
41
+ # Acts As Capybara Session
42
+ # Hack to support `Capybara::Session` without having to load
43
+ # Capybara or catch `NameError`s for the undefined constants
44
+ ::ActionDispatch::TestResponse.new.tap do |resp|
45
+ resp.status = obj.status_code
46
+ resp.headers = obj.response_headers
47
+ resp.body = obj.body
48
+ end
49
+ else
50
+ raise TypeError, "Invalid response type: #{obj}"
51
+ end
176
52
  end
177
- end
178
-
179
- # Reverse lookup of the Rack status code symbol based on the numeric http
180
- # code
181
- #
182
- # @param code [Fixnum] http status code to look up
183
- # @return [Symbol] representing the http numeric code
184
- def compute_status_from(code)
185
- status, _ = Rack::Utils::SYMBOL_TO_STATUS_CODE.find{ |_, c| c == code }
186
- status
187
- end
188
-
189
- # @return [String] pretty format the actual response status
190
- def pp_actual
191
- pp_status(actual_status, actual)
192
- end
193
-
194
- # @return [String] pretty format the expected status and associated code
195
- def pp_expected
196
- pp_status(expected_status, expected)
197
- end
198
-
199
- # @return [String] pretty format the actual response status
200
- def pp_status(status, code)
201
- if status
202
- "#{status.inspect} (#{code})"
203
- else
204
- code.to_s
53
+ module_function :as_test_response
54
+
55
+ # @return [String, nil] a formatted failure message if
56
+ # `@invalid_response` is present, `nil` otherwise
57
+ def invalid_response_type_message
58
+ return unless @invalid_response
59
+ "expected a response object, but an instance of " \
60
+ "#{@invalid_response.class} was received"
205
61
  end
206
- end
207
-
208
- # Sets `expected` to the numeric http code based on the Rack
209
- # `expected_status` status
210
- #
211
- # @see Rack::Utils::SYMBOL_TO_STATUS_CODE
212
- # @return [nil] if an associated code could not be found
213
- def set_expected_code!
214
- @expected ||= Rack::Utils::SYMBOL_TO_STATUS_CODE[expected_status]
215
- end
216
- end
217
62
 
218
- # @api private
219
- # Provides an implementation for `have_http_status` matching against
220
- # `ActionDispatch::TestResponse` http status category queries.
221
- #
222
- # Not intended to be instantiated directly.
223
- #
224
- # @example
225
- # expect(response).to have_http_status(:success)
226
- # expect(response).to have_http_status(:error)
227
- # expect(response).to have_http_status(:missing)
228
- # expect(response).to have_http_status(:redirect)
229
- #
230
- # @see RSpec::Rails::Matchers.have_http_status
231
- # @see ActionDispatch::TestResponse
232
- class GenericStatus < RSpec::Matchers::BuiltIn::BaseMatcher
233
- include HaveHttpStatus
234
-
235
- # @return [Array<Symbol>] of status codes which represent a HTTP status
236
- # code "group"
237
- # @see https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/testing/test_response.rb `ActionDispatch::TestResponse`
238
- def self.valid_statuses
239
- [:error, :success, :missing, :redirect]
240
- end
241
-
242
- def initialize(type)
243
- unless self.class.valid_statuses.include?(type)
244
- raise ArgumentError, "Invalid generic HTTP status: #{type.inspect}"
63
+ # @api private
64
+ # Provides an implementation for `have_http_status` matching against
65
+ # numeric http status codes.
66
+ #
67
+ # Not intended to be instantiated directly.
68
+ #
69
+ # @example
70
+ # expect(response).to have_http_status(404)
71
+ #
72
+ # @see RSpec::Rails::Matchers.have_http_status
73
+ class NumericCode < RSpec::Matchers::BuiltIn::BaseMatcher
74
+ include HaveHttpStatus
75
+
76
+ def initialize(code)
77
+ @expected = code.to_i
78
+ @actual = nil
79
+ @invalid_response = nil
80
+ end
81
+
82
+ # @param [Object] response object providing an http code to match
83
+ # @return [Boolean] `true` if the numeric code matched the `response` code
84
+ def matches?(response)
85
+ test_response = as_test_response(response)
86
+ @actual = test_response.response_code
87
+ expected == @actual
88
+ rescue TypeError => _ignored
89
+ @invalid_response = response
90
+ false
91
+ end
92
+
93
+ # @return [String]
94
+ def description
95
+ "respond with numeric status code #{expected}"
96
+ end
97
+
98
+ # @return [String] explaining why the match failed
99
+ def failure_message
100
+ invalid_response_type_message ||
101
+ "expected the response to have status code #{expected.inspect}" \
102
+ " but it was #{actual.inspect}"
103
+ end
104
+
105
+ # @return [String] explaining why the match failed
106
+ def failure_message_when_negated
107
+ invalid_response_type_message ||
108
+ "expected the response not to have status code " \
109
+ "#{expected.inspect} but it did"
110
+ end
245
111
  end
246
- @expected = type
247
- @actual = nil
248
- @invalid_response = nil
249
- end
250
-
251
- # @return [Boolean] `true` if Rack's associated numeric HTTP code matched
252
- # the `response` code
253
- def matches?(response)
254
- test_response = as_test_response(response)
255
- @actual = test_response.response_code
256
- test_response.send("#{expected}?")
257
- rescue TypeError => _ignored
258
- @invalid_response = response
259
- false
260
- end
261
-
262
- # @return [String]
263
- def description
264
- "respond with #{type_message}"
265
- end
266
-
267
- # @return [String] explaining why the match failed
268
- def failure_message
269
- invalid_response_type_message ||
270
- "expected the response to have #{type_message} but it was #{actual}"
271
- end
272
-
273
- # @return [String] explaining why the match failed
274
- def failure_message_when_negated
275
- invalid_response_type_message ||
276
- "expected the response not to have #{type_message} but it was #{actual}"
277
- end
278
-
279
- private
280
112
 
281
- # @return [String] formating the expected status and associated code(s)
282
- def type_message
283
- @type_message ||= (expected == :error ? "an error" : "a #{expected}") +
284
- " status code (#{type_codes})"
285
- end
286
-
287
- # @return [String] formatting the associated code(s) for the various
288
- # status code "groups"
289
- # @see https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/testing/test_response.rb `ActionDispatch::TestResponse`
290
- # @see https://github.com/rack/rack/blob/master/lib/rack/response.rb `Rack::Response`
291
- def type_codes
292
- # At the time of this commit the most recent version of
293
- # `ActionDispatch::TestResponse` defines the following aliases:
113
+ # @api private
114
+ # Provides an implementation for `have_http_status` matching against
115
+ # Rack symbol http status codes.
294
116
  #
295
- # alias_method :success?, :successful?
296
- # alias_method :missing?, :not_found?
297
- # alias_method :redirect?, :redirection?
298
- # alias_method :error?, :server_error?
117
+ # Not intended to be instantiated directly.
299
118
  #
300
- # It's parent `ActionDispatch::Response` includes
301
- # `Rack::Response::Helpers` which defines the aliased methods as:
119
+ # @example
120
+ # expect(response).to have_http_status(:created)
302
121
  #
303
- # def successful?; status >= 200 && status < 300; end
304
- # def redirection?; status >= 300 && status < 400; end
305
- # def server_error?; status >= 500 && status < 600; end
306
- # def not_found?; status == 404; end
122
+ # @see RSpec::Rails::Matchers.have_http_status
123
+ # @see https://github.com/rack/rack/blob/master/lib/rack/utils.rb `Rack::Utils::SYMBOL_TO_STATUS_CODE`
124
+ class SymbolicStatus < RSpec::Matchers::BuiltIn::BaseMatcher
125
+ include HaveHttpStatus
126
+
127
+ def initialize(status)
128
+ @expected_status = status
129
+ @actual = nil
130
+ @invalid_response = nil
131
+ set_expected_code!
132
+ end
133
+
134
+ # @param [Object] response object providing an http code to match
135
+ # @return [Boolean] `true` if Rack's associated numeric HTTP code matched
136
+ # the `response` code
137
+ def matches?(response)
138
+ test_response = as_test_response(response)
139
+ @actual = test_response.response_code
140
+ expected == @actual
141
+ rescue TypeError => _ignored
142
+ @invalid_response = response
143
+ false
144
+ end
145
+
146
+ # @return [String]
147
+ def description
148
+ "respond with status code #{pp_expected}"
149
+ end
150
+
151
+ # @return [String] explaining why the match failed
152
+ def failure_message
153
+ invalid_response_type_message ||
154
+ "expected the response to have status code #{pp_expected} but it" \
155
+ " was #{pp_actual}"
156
+ end
157
+
158
+ # @return [String] explaining why the match failed
159
+ def failure_message_when_negated
160
+ invalid_response_type_message ||
161
+ "expected the response not to have status code #{pp_expected} " \
162
+ "but it did"
163
+ end
164
+
165
+ # The initialized expected status symbol
166
+ attr_reader :expected_status
167
+ private :expected_status
168
+
169
+ private
170
+
171
+ # @return [Symbol] representing the actual http numeric code
172
+ def actual_status
173
+ return unless actual
174
+ @actual_status ||= compute_status_from(actual)
175
+ end
176
+
177
+ # Reverse lookup of the Rack status code symbol based on the numeric
178
+ # http code
179
+ #
180
+ # @param code [Fixnum] http status code to look up
181
+ # @return [Symbol] representing the http numeric code
182
+ def compute_status_from(code)
183
+ status, _ = Rack::Utils::SYMBOL_TO_STATUS_CODE.find do |_, c|
184
+ c == code
185
+ end
186
+ status
187
+ end
188
+
189
+ # @return [String] pretty format the actual response status
190
+ def pp_actual
191
+ pp_status(actual_status, actual)
192
+ end
193
+
194
+ # @return [String] pretty format the expected status and associated code
195
+ def pp_expected
196
+ pp_status(expected_status, expected)
197
+ end
198
+
199
+ # @return [String] pretty format the actual response status
200
+ def pp_status(status, code)
201
+ if status
202
+ "#{status.inspect} (#{code})"
203
+ else
204
+ code.to_s
205
+ end
206
+ end
207
+
208
+ # Sets `expected` to the numeric http code based on the Rack
209
+ # `expected_status` status
210
+ #
211
+ # @see Rack::Utils::SYMBOL_TO_STATUS_CODE
212
+ # @raise [ArgumentError] if an associated code could not be found
213
+ def set_expected_code!
214
+ @expected ||=
215
+ Rack::Utils::SYMBOL_TO_STATUS_CODE.fetch(expected_status) do
216
+ raise ArgumentError,
217
+ "Invalid HTTP status: #{expected_status.inspect}"
218
+ end
219
+ end
220
+ end
221
+
222
+ # @api private
223
+ # Provides an implementation for `have_http_status` matching against
224
+ # `ActionDispatch::TestResponse` http status category queries.
225
+ #
226
+ # Not intended to be instantiated directly.
307
227
  #
308
- # @see https://github.com/rails/rails/blob/ca200378/actionpack/lib/action_dispatch/testing/test_response.rb#L17-L27
309
- # @see https://github.com/rails/rails/blob/ca200378/actionpack/lib/action_dispatch/http/response.rb#L74
310
- # @see https://github.com/rack/rack/blob/ce4a3959/lib/rack/response.rb#L119-L122
311
- @type_codes ||= case expected
312
- when :error
313
- "5xx"
314
- when :success
315
- "2xx"
316
- when :missing
317
- "404"
318
- when :redirect
319
- "3xx"
320
- end
228
+ # @example
229
+ # expect(response).to have_http_status(:success)
230
+ # expect(response).to have_http_status(:error)
231
+ # expect(response).to have_http_status(:missing)
232
+ # expect(response).to have_http_status(:redirect)
233
+ #
234
+ # @see RSpec::Rails::Matchers.have_http_status
235
+ # @see ActionDispatch::TestResponse
236
+ class GenericStatus < RSpec::Matchers::BuiltIn::BaseMatcher
237
+ include HaveHttpStatus
238
+
239
+ # @return [Array<Symbol>] of status codes which represent a HTTP status
240
+ # code "group"
241
+ # @see https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/testing/test_response.rb `ActionDispatch::TestResponse`
242
+ def self.valid_statuses
243
+ [:error, :success, :missing, :redirect]
244
+ end
245
+
246
+ def initialize(type)
247
+ unless self.class.valid_statuses.include?(type)
248
+ raise ArgumentError, "Invalid generic HTTP status: #{type.inspect}"
249
+ end
250
+ @expected = type
251
+ @actual = nil
252
+ @invalid_response = nil
253
+ end
254
+
255
+ # @return [Boolean] `true` if Rack's associated numeric HTTP code matched
256
+ # the `response` code
257
+ def matches?(response)
258
+ test_response = as_test_response(response)
259
+ @actual = test_response.response_code
260
+ test_response.send("#{expected}?")
261
+ rescue TypeError => _ignored
262
+ @invalid_response = response
263
+ false
264
+ end
265
+
266
+ # @return [String]
267
+ def description
268
+ "respond with #{type_message}"
269
+ end
270
+
271
+ # @return [String] explaining why the match failed
272
+ def failure_message
273
+ invalid_response_type_message ||
274
+ "expected the response to have #{type_message} but it was #{actual}"
275
+ end
276
+
277
+ # @return [String] explaining why the match failed
278
+ def failure_message_when_negated
279
+ invalid_response_type_message ||
280
+ "expected the response not to have #{type_message} but it was #{actual}"
281
+ end
282
+
283
+ private
284
+
285
+ # @return [String] formating the expected status and associated code(s)
286
+ def type_message
287
+ @type_message ||= (expected == :error ? "an error" : "a #{expected}") +
288
+ " status code (#{type_codes})"
289
+ end
290
+
291
+ # @return [String] formatting the associated code(s) for the various
292
+ # status code "groups"
293
+ # @see https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/testing/test_response.rb `ActionDispatch::TestResponse`
294
+ # @see https://github.com/rack/rack/blob/master/lib/rack/response.rb `Rack::Response`
295
+ def type_codes
296
+ # At the time of this commit the most recent version of
297
+ # `ActionDispatch::TestResponse` defines the following aliases:
298
+ #
299
+ # alias_method :success?, :successful?
300
+ # alias_method :missing?, :not_found?
301
+ # alias_method :redirect?, :redirection?
302
+ # alias_method :error?, :server_error?
303
+ #
304
+ # It's parent `ActionDispatch::Response` includes
305
+ # `Rack::Response::Helpers` which defines the aliased methods as:
306
+ #
307
+ # def successful?; status >= 200 && status < 300; end
308
+ # def redirection?; status >= 300 && status < 400; end
309
+ # def server_error?; status >= 500 && status < 600; end
310
+ # def not_found?; status == 404; end
311
+ #
312
+ # @see https://github.com/rails/rails/blob/ca200378/actionpack/lib/action_dispatch/testing/test_response.rb#L17-L27
313
+ # @see https://github.com/rails/rails/blob/ca200378/actionpack/lib/action_dispatch/http/response.rb#L74
314
+ # @see https://github.com/rack/rack/blob/ce4a3959/lib/rack/response.rb#L119-L122
315
+ @type_codes ||= case expected
316
+ when :error
317
+ "5xx"
318
+ when :success
319
+ "2xx"
320
+ when :missing
321
+ "404"
322
+ when :redirect
323
+ "3xx"
324
+ end
325
+ end
326
+ end
321
327
  end
322
- end
323
- end
324
328
 
325
- # @api public
326
- # Passes if `response` has a matching HTTP status code.
327
- #
328
- # The following symbolic status codes are allowed:
329
- #
330
- # - `Rack::Utils::SYMBOL_TO_STATUS_CODE`
331
- # - One of the defined `ActionDispatch::TestResponse` aliases:
332
- # - `:error`
333
- # - `:missing`
334
- # - `:redirect`
335
- # - `:success`
336
- #
337
- # @example Accepts numeric and symbol statuses
338
- # expect(response).to have_http_status(404)
339
- # expect(response).to have_http_status(:created)
340
- # expect(response).to have_http_status(:success)
341
- # expect(response).to have_http_status(:error)
342
- # expect(response).to have_http_status(:missing)
343
- # expect(response).to have_http_status(:redirect)
344
- #
345
- # @example Works with standard `response` objects and Capybara's `page`
346
- # expect(response).to have_http_status(404)
347
- # expect(page).to have_http_status(:created)
348
- #
349
- # @see https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/testing/test_response.rb `ActionDispatch::TestResponse`
350
- # @see https://github.com/rack/rack/blob/master/lib/rack/utils.rb `Rack::Utils::SYMBOL_TO_STATUS_CODE`
351
- def have_http_status(target)
352
- raise ArgumentError, "Invalid HTTP status: nil" unless target
353
- HaveHttpStatus.matcher_for_status(target)
329
+ # @api public
330
+ # Passes if `response` has a matching HTTP status code.
331
+ #
332
+ # The following symbolic status codes are allowed:
333
+ #
334
+ # - `Rack::Utils::SYMBOL_TO_STATUS_CODE`
335
+ # - One of the defined `ActionDispatch::TestResponse` aliases:
336
+ # - `:error`
337
+ # - `:missing`
338
+ # - `:redirect`
339
+ # - `:success`
340
+ #
341
+ # @example Accepts numeric and symbol statuses
342
+ # expect(response).to have_http_status(404)
343
+ # expect(response).to have_http_status(:created)
344
+ # expect(response).to have_http_status(:success)
345
+ # expect(response).to have_http_status(:error)
346
+ # expect(response).to have_http_status(:missing)
347
+ # expect(response).to have_http_status(:redirect)
348
+ #
349
+ # @example Works with standard `response` objects and Capybara's `page`
350
+ # expect(response).to have_http_status(404)
351
+ # expect(page).to have_http_status(:created)
352
+ #
353
+ # @see https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/testing/test_response.rb `ActionDispatch::TestResponse`
354
+ # @see https://github.com/rack/rack/blob/master/lib/rack/utils.rb `Rack::Utils::SYMBOL_TO_STATUS_CODE`
355
+ def have_http_status(target)
356
+ raise ArgumentError, "Invalid HTTP status: nil" unless target
357
+ HaveHttpStatus.matcher_for_status(target)
358
+ end
359
+ end
354
360
  end
355
361
  end