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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/Changelog.md +17 -0
- data/README.md +7 -7
- data/lib/generators/rspec.rb +10 -6
- data/lib/generators/rspec/controller/templates/controller_spec.rb +1 -1
- data/lib/generators/rspec/install/install_generator.rb +19 -2
- data/lib/generators/rspec/install/templates/spec/rails_helper.rb +13 -4
- data/lib/generators/rspec/integration/templates/request_spec.rb +1 -1
- data/lib/generators/rspec/job/job_generator.rb +12 -0
- data/lib/generators/rspec/job/templates/job_spec.rb.erb +7 -0
- data/lib/generators/rspec/model/model_generator.rb +18 -5
- data/lib/generators/rspec/scaffold/scaffold_generator.rb +107 -100
- data/lib/rspec-rails.rb +1 -1
- data/lib/rspec/rails.rb +1 -0
- data/lib/rspec/rails/adapters.rb +9 -7
- data/lib/rspec/rails/configuration.rb +2 -3
- data/lib/rspec/rails/example/controller_example_group.rb +172 -149
- data/lib/rspec/rails/example/feature_example_group.rb +25 -23
- data/lib/rspec/rails/example/helper_example_group.rb +27 -25
- data/lib/rspec/rails/example/mailer_example_group.rb +26 -22
- data/lib/rspec/rails/example/model_example_group.rb +8 -6
- data/lib/rspec/rails/example/request_example_group.rb +19 -17
- data/lib/rspec/rails/example/routing_example_group.rb +40 -38
- data/lib/rspec/rails/example/view_example_group.rb +140 -137
- data/lib/rspec/rails/extensions/active_record/proxy.rb +0 -1
- data/lib/rspec/rails/feature_check.rb +35 -0
- data/lib/rspec/rails/fixture_support.rb +1 -1
- data/lib/rspec/rails/matchers.rb +5 -2
- data/lib/rspec/rails/matchers/be_a_new.rb +68 -64
- data/lib/rspec/rails/matchers/be_new_record.rb +24 -20
- data/lib/rspec/rails/matchers/be_valid.rb +38 -34
- data/lib/rspec/rails/matchers/have_http_status.rb +340 -334
- data/lib/rspec/rails/matchers/have_rendered.rb +35 -32
- data/lib/rspec/rails/matchers/redirect_to.rb +30 -27
- data/lib/rspec/rails/matchers/routing_matchers.rb +103 -101
- data/lib/rspec/rails/version.rb +1 -1
- data/lib/rspec/rails/view_assigns.rb +1 -2
- data/lib/rspec/rails/view_rendering.rb +6 -8
- metadata +16 -13
- metadata.gz.sig +3 -2
@@ -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
|
data/lib/rspec/rails/matchers.rb
CHANGED
@@ -1,77 +1,81 @@
|
|
1
|
-
module RSpec
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
44
|
+
private
|
43
45
|
|
44
|
-
|
45
|
-
|
46
|
-
|
46
|
+
def attributes
|
47
|
+
@attributes ||= {}
|
48
|
+
end
|
47
49
|
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
55
|
-
|
56
|
-
|
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
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
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
|
-
|
9
|
-
|
10
|
-
|
10
|
+
def failure_message
|
11
|
+
"expected #{actual.inspect} to be a new record, but was persisted"
|
12
|
+
end
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
-
|
14
|
-
|
25
|
+
message << ", but got errors: #{errors.map(&:to_s).join(', ')}"
|
26
|
+
end
|
15
27
|
|
16
|
-
|
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
|
-
|
31
|
+
def failure_message_when_negated
|
32
|
+
"expected #{actual.inspect} not to be valid"
|
33
|
+
end
|
24
34
|
end
|
25
35
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
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
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
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
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
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
|
-
|
282
|
-
|
283
|
-
|
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
|
-
#
|
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
|
-
#
|
301
|
-
#
|
119
|
+
# @example
|
120
|
+
# expect(response).to have_http_status(:created)
|
302
121
|
#
|
303
|
-
#
|
304
|
-
#
|
305
|
-
|
306
|
-
|
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
|
-
# @
|
309
|
-
#
|
310
|
-
#
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
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
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
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
|