rspec-rails 3.0.2 → 3.1.0

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