rspec-rails 3.7.0 → 4.0.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 +5 -5
- checksums.yaml.gz.sig +0 -0
- data/Capybara.md +5 -54
- data/Changelog.md +259 -70
- data/README.md +265 -496
- data/lib/generators/rspec/channel/channel_generator.rb +12 -0
- data/lib/generators/rspec/{observer/templates/observer_spec.rb → channel/templates/channel_spec.rb.erb} +1 -1
- data/lib/generators/rspec/controller/controller_generator.rb +21 -4
- data/lib/generators/rspec/controller/templates/request_spec.rb +14 -0
- data/lib/generators/rspec/controller/templates/routing_spec.rb +13 -0
- data/lib/generators/rspec/feature/feature_generator.rb +4 -4
- data/lib/generators/rspec/generator/generator_generator.rb +24 -0
- data/lib/generators/rspec/generator/templates/generator_spec.rb +6 -0
- data/lib/generators/rspec/helper/helper_generator.rb +1 -1
- data/lib/generators/rspec/install/install_generator.rb +4 -4
- data/lib/generators/rspec/install/templates/spec/rails_helper.rb +25 -12
- data/lib/generators/rspec/integration/integration_generator.rb +4 -4
- data/lib/generators/rspec/integration/templates/request_spec.rb +1 -1
- data/lib/generators/rspec/mailbox/mailbox_generator.rb +14 -0
- data/lib/generators/rspec/mailbox/templates/mailbox_spec.rb.erb +7 -0
- data/lib/generators/rspec/mailer/mailer_generator.rb +2 -1
- data/lib/generators/rspec/mailer/templates/preview.rb +1 -1
- data/lib/generators/rspec/model/model_generator.rb +6 -5
- data/lib/generators/rspec/model/templates/fixtures.yml +1 -1
- data/lib/generators/rspec/request/request_generator.rb +1 -1
- data/lib/generators/rspec/scaffold/scaffold_generator.rb +43 -23
- data/lib/generators/rspec/scaffold/templates/api_controller_spec.rb +2 -38
- data/lib/generators/rspec/scaffold/templates/api_request_spec.rb +131 -0
- data/lib/generators/rspec/scaffold/templates/controller_spec.rb +17 -17
- data/lib/generators/rspec/scaffold/templates/edit_spec.rb +1 -1
- data/lib/generators/rspec/scaffold/templates/index_spec.rb +2 -2
- data/lib/generators/rspec/scaffold/templates/new_spec.rb +1 -1
- data/lib/generators/rspec/scaffold/templates/request_spec.rb +133 -0
- data/lib/generators/rspec/scaffold/templates/routing_spec.rb +10 -13
- data/lib/generators/rspec/scaffold/templates/show_spec.rb +1 -1
- data/lib/generators/rspec/system/system_generator.rb +26 -0
- data/lib/generators/rspec/system/templates/system_spec.rb +9 -0
- data/lib/generators/rspec/view/view_generator.rb +2 -2
- data/lib/generators/rspec.rb +0 -6
- data/lib/rspec/rails/adapters.rb +11 -76
- data/lib/rspec/rails/configuration.rb +47 -36
- data/lib/rspec/rails/example/channel_example_group.rb +93 -0
- data/lib/rspec/rails/example/controller_example_group.rb +4 -4
- data/lib/rspec/rails/example/feature_example_group.rb +6 -26
- data/lib/rspec/rails/example/helper_example_group.rb +2 -9
- data/lib/rspec/rails/example/mailbox_example_group.rb +80 -0
- data/lib/rspec/rails/example/mailer_example_group.rb +1 -1
- data/lib/rspec/rails/example/rails_example_group.rb +1 -1
- data/lib/rspec/rails/example/system_example_group.rb +96 -60
- data/lib/rspec/rails/example/view_example_group.rb +47 -28
- data/lib/rspec/rails/example.rb +3 -3
- data/lib/rspec/rails/extensions/active_record/proxy.rb +1 -9
- data/lib/rspec/rails/feature_check.rb +12 -29
- data/lib/rspec/rails/fixture_file_upload_support.rb +40 -0
- data/lib/rspec/rails/fixture_support.rb +37 -31
- data/lib/rspec/rails/matchers/action_cable/have_broadcasted_to.rb +170 -0
- data/lib/rspec/rails/matchers/action_cable/have_streams.rb +58 -0
- data/lib/rspec/rails/matchers/action_cable.rb +65 -0
- data/lib/rspec/rails/matchers/action_mailbox.rb +64 -0
- data/lib/rspec/rails/matchers/active_job.rb +180 -22
- data/lib/rspec/rails/matchers/base_matcher.rb +179 -0
- data/lib/rspec/rails/matchers/be_a_new.rb +1 -1
- data/lib/rspec/rails/matchers/be_new_record.rb +1 -1
- data/lib/rspec/rails/matchers/be_valid.rb +1 -1
- data/lib/rspec/rails/matchers/have_enqueued_mail.rb +198 -0
- data/lib/rspec/rails/matchers/have_http_status.rb +34 -13
- data/lib/rspec/rails/matchers/have_rendered.rb +2 -1
- data/lib/rspec/rails/matchers/redirect_to.rb +1 -1
- data/lib/rspec/rails/matchers/routing_matchers.rb +14 -14
- data/lib/rspec/rails/matchers.rb +11 -0
- data/lib/rspec/rails/tasks/rspec.rake +7 -17
- data/lib/rspec/rails/vendor/capybara.rb +10 -15
- data/lib/rspec/rails/version.rb +1 -1
- data/lib/rspec/rails/view_path_builder.rb +1 -1
- data/lib/rspec/rails/view_rendering.rb +16 -5
- data/lib/rspec/rails.rb +1 -0
- data/lib/rspec-rails.rb +13 -10
- data.tar.gz.sig +0 -0
- metadata +55 -33
- metadata.gz.sig +0 -0
- data/lib/generators/rspec/observer/observer_generator.rb +0 -13
@@ -0,0 +1,179 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Rails
|
3
|
+
module Matchers
|
4
|
+
# @api private
|
5
|
+
#
|
6
|
+
# Base class to build matchers. Should not be instantiated directly.
|
7
|
+
class BaseMatcher
|
8
|
+
include RSpec::Matchers::Composable
|
9
|
+
|
10
|
+
# @api private
|
11
|
+
# Used to detect when no arg is passed to `initialize`.
|
12
|
+
# `nil` cannot be used because it's a valid value to pass.
|
13
|
+
UNDEFINED = Object.new.freeze
|
14
|
+
|
15
|
+
# @private
|
16
|
+
attr_reader :actual, :expected, :rescued_exception
|
17
|
+
|
18
|
+
# @private
|
19
|
+
attr_writer :matcher_name
|
20
|
+
|
21
|
+
def initialize(expected = UNDEFINED)
|
22
|
+
@expected = expected unless UNDEFINED.equal?(expected)
|
23
|
+
end
|
24
|
+
|
25
|
+
# @api private
|
26
|
+
# Indicates if the match is successful. Delegates to `match`, which
|
27
|
+
# should be defined on a subclass. Takes care of consistently
|
28
|
+
# initializing the `actual` attribute.
|
29
|
+
def matches?(actual)
|
30
|
+
@actual = actual
|
31
|
+
match(expected, actual)
|
32
|
+
end
|
33
|
+
|
34
|
+
# @api private
|
35
|
+
# Used to wrap a block of code that will indicate failure by
|
36
|
+
# raising one of the named exceptions.
|
37
|
+
#
|
38
|
+
# This is used by rspec-rails for some of its matchers that
|
39
|
+
# wrap rails' assertions.
|
40
|
+
def match_unless_raises(*exceptions)
|
41
|
+
exceptions.unshift Exception if exceptions.empty?
|
42
|
+
begin
|
43
|
+
yield
|
44
|
+
true
|
45
|
+
rescue *exceptions => @rescued_exception
|
46
|
+
false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# @api private
|
51
|
+
# Generates a description using {RSpec::Matchers::EnglishPhrasing}.
|
52
|
+
# @return [String]
|
53
|
+
def description
|
54
|
+
desc = RSpec::Matchers::EnglishPhrasing.split_words(self.class.matcher_name)
|
55
|
+
desc << RSpec::Matchers::EnglishPhrasing.list(@expected) if defined?(@expected)
|
56
|
+
desc
|
57
|
+
end
|
58
|
+
|
59
|
+
# @api private
|
60
|
+
# Matchers are not diffable by default. Override this to make your
|
61
|
+
# subclass diffable.
|
62
|
+
def diffable?
|
63
|
+
false
|
64
|
+
end
|
65
|
+
|
66
|
+
# @api private
|
67
|
+
# Most matchers are value matchers (i.e. meant to work with `expect(value)`)
|
68
|
+
# rather than block matchers (i.e. meant to work with `expect { }`), so
|
69
|
+
# this defaults to false. Block matchers must override this to return true.
|
70
|
+
def supports_block_expectations?
|
71
|
+
false
|
72
|
+
end
|
73
|
+
|
74
|
+
# @api private
|
75
|
+
def expects_call_stack_jump?
|
76
|
+
false
|
77
|
+
end
|
78
|
+
|
79
|
+
# @private
|
80
|
+
def expected_formatted
|
81
|
+
RSpec::Support::ObjectFormatter.format(@expected)
|
82
|
+
end
|
83
|
+
|
84
|
+
# @private
|
85
|
+
def actual_formatted
|
86
|
+
RSpec::Support::ObjectFormatter.format(@actual)
|
87
|
+
end
|
88
|
+
|
89
|
+
# @private
|
90
|
+
def self.matcher_name
|
91
|
+
@matcher_name ||= underscore(name.split('::').last)
|
92
|
+
end
|
93
|
+
|
94
|
+
# @private
|
95
|
+
def matcher_name
|
96
|
+
if defined?(@matcher_name)
|
97
|
+
@matcher_name
|
98
|
+
else
|
99
|
+
self.class.matcher_name
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# @private
|
104
|
+
# Borrowed from ActiveSupport.
|
105
|
+
def self.underscore(camel_cased_word)
|
106
|
+
word = camel_cased_word.to_s.dup
|
107
|
+
word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
108
|
+
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
109
|
+
word.tr!('-', '_')
|
110
|
+
word.downcase!
|
111
|
+
word
|
112
|
+
end
|
113
|
+
private_class_method :underscore
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def assert_ivars(*expected_ivars)
|
118
|
+
return unless (expected_ivars - present_ivars).any?
|
119
|
+
|
120
|
+
ivar_list = RSpec::Matchers::EnglishPhrasing.list(expected_ivars)
|
121
|
+
raise "#{self.class.name} needs to supply#{ivar_list}"
|
122
|
+
end
|
123
|
+
|
124
|
+
alias present_ivars instance_variables
|
125
|
+
|
126
|
+
# @private
|
127
|
+
module HashFormatting
|
128
|
+
# `{ :a => 5, :b => 2 }.inspect` produces:
|
129
|
+
#
|
130
|
+
# {:a=>5, :b=>2}
|
131
|
+
#
|
132
|
+
# ...but it looks much better as:
|
133
|
+
#
|
134
|
+
# {:a => 5, :b => 2}
|
135
|
+
#
|
136
|
+
# This is idempotent and safe to run on a string multiple times.
|
137
|
+
def improve_hash_formatting(inspect_string)
|
138
|
+
inspect_string.gsub(/(\S)=>(\S)/, '\1 => \2')
|
139
|
+
end
|
140
|
+
module_function :improve_hash_formatting
|
141
|
+
end
|
142
|
+
|
143
|
+
include HashFormatting
|
144
|
+
|
145
|
+
# @api private
|
146
|
+
# Provides default implementations of failure messages, based on the `description`.
|
147
|
+
module DefaultFailureMessages
|
148
|
+
# @api private
|
149
|
+
# Provides a good generic failure message. Based on `description`.
|
150
|
+
# When subclassing, if you are not satisfied with this failure message
|
151
|
+
# you often only need to override `description`.
|
152
|
+
# @return [String]
|
153
|
+
def failure_message
|
154
|
+
"expected #{description_of @actual} to #{description}".dup
|
155
|
+
end
|
156
|
+
|
157
|
+
# @api private
|
158
|
+
# Provides a good generic negative failure message. Based on `description`.
|
159
|
+
# When subclassing, if you are not satisfied with this failure message
|
160
|
+
# you often only need to override `description`.
|
161
|
+
# @return [String]
|
162
|
+
def failure_message_when_negated
|
163
|
+
"expected #{description_of @actual} not to #{description}".dup
|
164
|
+
end
|
165
|
+
|
166
|
+
# @private
|
167
|
+
def self.has_default_failure_messages?(matcher)
|
168
|
+
matcher.method(:failure_message).owner == self &&
|
169
|
+
matcher.method(:failure_message_when_negated).owner == self
|
170
|
+
rescue NameError
|
171
|
+
false
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
include DefaultFailureMessages
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -6,7 +6,7 @@ module RSpec
|
|
6
6
|
# Matcher class for `be_a_new`. Should not be instantiated directly.
|
7
7
|
#
|
8
8
|
# @see RSpec::Rails::Matchers#be_a_new
|
9
|
-
class BeANew < RSpec::Matchers::
|
9
|
+
class BeANew < RSpec::Rails::Matchers::BaseMatcher
|
10
10
|
# @private
|
11
11
|
def initialize(expected)
|
12
12
|
@expected = expected
|
@@ -15,7 +15,7 @@ module RSpec
|
|
15
15
|
def failure_message
|
16
16
|
message = "expected #{actual.inspect} to be valid"
|
17
17
|
|
18
|
-
if actual.respond_to?(:errors)
|
18
|
+
if actual.respond_to?(:errors) && actual.method(:errors).arity < 1
|
19
19
|
errors = if actual.errors.respond_to?(:full_messages)
|
20
20
|
actual.errors.full_messages
|
21
21
|
else
|
@@ -0,0 +1,198 @@
|
|
1
|
+
# We require the minimum amount of rspec-mocks possible to avoid
|
2
|
+
# conflicts with other mocking frameworks.
|
3
|
+
# See: https://github.com/rspec/rspec-rails/issues/2252
|
4
|
+
require "rspec/mocks/argument_matchers"
|
5
|
+
require "rspec/rails/matchers/active_job"
|
6
|
+
|
7
|
+
module RSpec
|
8
|
+
module Rails
|
9
|
+
module Matchers
|
10
|
+
# Matcher class for `have_enqueued_mail`. Should not be instantiated directly.
|
11
|
+
#
|
12
|
+
# @private
|
13
|
+
# @see RSpec::Rails::Matchers#have_enqueued_mail
|
14
|
+
class HaveEnqueuedMail < ActiveJob::HaveEnqueuedJob
|
15
|
+
MAILER_JOB_METHOD = 'deliver_now'.freeze
|
16
|
+
|
17
|
+
include RSpec::Mocks::ArgumentMatchers
|
18
|
+
|
19
|
+
def initialize(mailer_class, method_name)
|
20
|
+
super(nil)
|
21
|
+
@mailer_class = mailer_class
|
22
|
+
@method_name = method_name
|
23
|
+
@mail_args = []
|
24
|
+
end
|
25
|
+
|
26
|
+
def description
|
27
|
+
"enqueues #{mailer_class_name}.#{@method_name}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def with(*args, &block)
|
31
|
+
@mail_args = args
|
32
|
+
block.nil? ? super : super(&yield_mail_args(block))
|
33
|
+
end
|
34
|
+
|
35
|
+
def matches?(block)
|
36
|
+
raise ArgumentError, 'have_enqueued_mail and enqueue_mail only work with block arguments' unless block.respond_to?(:call)
|
37
|
+
|
38
|
+
check_active_job_adapter
|
39
|
+
super
|
40
|
+
end
|
41
|
+
|
42
|
+
def failure_message
|
43
|
+
"expected to enqueue #{base_message}".tap do |msg|
|
44
|
+
msg << "\n#{unmatching_mail_jobs_message}" if unmatching_mail_jobs.any?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def failure_message_when_negated
|
49
|
+
"expected not to enqueue #{base_message}"
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def base_message
|
55
|
+
[mailer_class_name, @method_name].compact.join('.').tap do |msg|
|
56
|
+
msg << " #{expected_count_message}"
|
57
|
+
msg << " with #{@mail_args}," if @mail_args.any?
|
58
|
+
msg << " on queue #{@queue}," if @queue
|
59
|
+
msg << " at #{@at.inspect}," if @at
|
60
|
+
msg << " but enqueued #{@matching_jobs.size}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def expected_count_message
|
65
|
+
"#{message_expectation_modifier} #{@expected_number} #{@expected_number == 1 ? 'time' : 'times'}"
|
66
|
+
end
|
67
|
+
|
68
|
+
def mailer_class_name
|
69
|
+
@mailer_class ? @mailer_class.name : 'ActionMailer::Base'
|
70
|
+
end
|
71
|
+
|
72
|
+
def job_match?(job)
|
73
|
+
legacy_mail?(job) || parameterized_mail?(job) || unified_mail?(job)
|
74
|
+
end
|
75
|
+
|
76
|
+
def arguments_match?(job)
|
77
|
+
@args =
|
78
|
+
if @mail_args.any?
|
79
|
+
base_mailer_args + @mail_args
|
80
|
+
elsif @mailer_class && @method_name
|
81
|
+
base_mailer_args + [any_args]
|
82
|
+
elsif @mailer_class
|
83
|
+
[mailer_class_name, any_args]
|
84
|
+
else
|
85
|
+
[]
|
86
|
+
end
|
87
|
+
|
88
|
+
super(job)
|
89
|
+
end
|
90
|
+
|
91
|
+
def base_mailer_args
|
92
|
+
[mailer_class_name, @method_name.to_s, MAILER_JOB_METHOD]
|
93
|
+
end
|
94
|
+
|
95
|
+
def yield_mail_args(block)
|
96
|
+
proc { |*job_args| block.call(*(job_args - base_mailer_args)) }
|
97
|
+
end
|
98
|
+
|
99
|
+
def check_active_job_adapter
|
100
|
+
return if ::ActiveJob::QueueAdapters::TestAdapter === ::ActiveJob::Base.queue_adapter
|
101
|
+
|
102
|
+
raise StandardError, "To use HaveEnqueuedMail matcher set `ActiveJob::Base.queue_adapter = :test`"
|
103
|
+
end
|
104
|
+
|
105
|
+
def unmatching_mail_jobs
|
106
|
+
@unmatching_jobs.select do |job|
|
107
|
+
job_match?(job)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def unmatching_mail_jobs_message
|
112
|
+
msg = "Queued deliveries:"
|
113
|
+
|
114
|
+
unmatching_mail_jobs.each do |job|
|
115
|
+
msg << "\n #{mail_job_message(job)}"
|
116
|
+
end
|
117
|
+
|
118
|
+
msg
|
119
|
+
end
|
120
|
+
|
121
|
+
def mail_job_message(job)
|
122
|
+
mailer_method = job[:args][0..1].join('.')
|
123
|
+
|
124
|
+
mailer_args = job[:args][3..-1]
|
125
|
+
msg_parts = []
|
126
|
+
msg_parts << "with #{mailer_args}" if mailer_args.any?
|
127
|
+
msg_parts << "on queue #{job[:queue]}" if job[:queue] && job[:queue] != 'mailers'
|
128
|
+
msg_parts << "at #{Time.at(job[:at])}" if job[:at]
|
129
|
+
|
130
|
+
"#{mailer_method} #{msg_parts.join(', ')}".strip
|
131
|
+
end
|
132
|
+
|
133
|
+
def legacy_mail?(job)
|
134
|
+
job[:job] == ActionMailer::DeliveryJob
|
135
|
+
end
|
136
|
+
|
137
|
+
def parameterized_mail?(job)
|
138
|
+
RSpec::Rails::FeatureCheck.has_action_mailer_parameterized? && job[:job] == ActionMailer::Parameterized::DeliveryJob
|
139
|
+
end
|
140
|
+
|
141
|
+
def unified_mail?(job)
|
142
|
+
RSpec::Rails::FeatureCheck.has_action_mailer_unified_delivery? && job[:job] == ActionMailer::MailDeliveryJob
|
143
|
+
end
|
144
|
+
end
|
145
|
+
# @api public
|
146
|
+
# Passes if an email has been enqueued inside block.
|
147
|
+
# May chain with to specify expected arguments.
|
148
|
+
# May chain at_least, at_most or exactly to specify a number of times.
|
149
|
+
# May chain at to specify a send time.
|
150
|
+
# May chain on_queue to specify a queue.
|
151
|
+
#
|
152
|
+
# @example
|
153
|
+
# expect {
|
154
|
+
# MyMailer.welcome(user).deliver_later
|
155
|
+
# }.to have_enqueued_mail
|
156
|
+
#
|
157
|
+
# expect {
|
158
|
+
# MyMailer.welcome(user).deliver_later
|
159
|
+
# }.to have_enqueued_mail(MyMailer)
|
160
|
+
#
|
161
|
+
# expect {
|
162
|
+
# MyMailer.welcome(user).deliver_later
|
163
|
+
# }.to have_enqueued_mail(MyMailer, :welcome)
|
164
|
+
#
|
165
|
+
# # Using alias
|
166
|
+
# expect {
|
167
|
+
# MyMailer.welcome(user).deliver_later
|
168
|
+
# }.to enqueue_mail(MyMailer, :welcome)
|
169
|
+
#
|
170
|
+
# expect {
|
171
|
+
# MyMailer.welcome(user).deliver_later
|
172
|
+
# }.to have_enqueued_mail(MyMailer, :welcome).with(user)
|
173
|
+
#
|
174
|
+
# expect {
|
175
|
+
# MyMailer.welcome(user).deliver_later
|
176
|
+
# MyMailer.welcome(user).deliver_later
|
177
|
+
# }.to have_enqueued_mail(MyMailer, :welcome).at_least(:once)
|
178
|
+
#
|
179
|
+
# expect {
|
180
|
+
# MyMailer.welcome(user).deliver_later
|
181
|
+
# }.to have_enqueued_mail(MyMailer, :welcome).at_most(:twice)
|
182
|
+
#
|
183
|
+
# expect {
|
184
|
+
# MyMailer.welcome(user).deliver_later(wait_until: Date.tomorrow.noon)
|
185
|
+
# }.to have_enqueued_mail(MyMailer, :welcome).at(Date.tomorrow.noon)
|
186
|
+
#
|
187
|
+
# expect {
|
188
|
+
# MyMailer.welcome(user).deliver_later(queue: :urgent_mail)
|
189
|
+
# }.to have_enqueued_mail(MyMailer, :welcome).on_queue(:urgent_mail)
|
190
|
+
def have_enqueued_mail(mailer_class = nil, mail_method_name = nil)
|
191
|
+
HaveEnqueuedMail.new(mailer_class, mail_method_name)
|
192
|
+
end
|
193
|
+
alias_method :have_enqueued_email, :have_enqueued_mail
|
194
|
+
alias_method :enqueue_mail, :have_enqueued_mail
|
195
|
+
alias_method :enqueue_email, :have_enqueued_mail
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
@@ -59,6 +59,7 @@ module RSpec
|
|
59
59
|
# `@invalid_response` is present, `nil` otherwise
|
60
60
|
def invalid_response_type_message
|
61
61
|
return unless @invalid_response
|
62
|
+
|
62
63
|
"expected a response object, but an instance of " \
|
63
64
|
"#{@invalid_response.class} was received"
|
64
65
|
end
|
@@ -72,8 +73,8 @@ module RSpec
|
|
72
73
|
# @example
|
73
74
|
# expect(response).to have_http_status(404)
|
74
75
|
#
|
75
|
-
# @see RSpec::Rails::Matchers
|
76
|
-
class NumericCode < RSpec::Matchers::
|
76
|
+
# @see RSpec::Rails::Matchers#have_http_status
|
77
|
+
class NumericCode < RSpec::Rails::Matchers::BaseMatcher
|
77
78
|
include HaveHttpStatus
|
78
79
|
|
79
80
|
def initialize(code)
|
@@ -122,9 +123,9 @@ module RSpec
|
|
122
123
|
# @example
|
123
124
|
# expect(response).to have_http_status(:created)
|
124
125
|
#
|
125
|
-
# @see RSpec::Rails::Matchers
|
126
|
+
# @see RSpec::Rails::Matchers#have_http_status
|
126
127
|
# @see https://github.com/rack/rack/blob/master/lib/rack/utils.rb `Rack::Utils::SYMBOL_TO_STATUS_CODE`
|
127
|
-
class SymbolicStatus < RSpec::Matchers::
|
128
|
+
class SymbolicStatus < RSpec::Rails::Matchers::BaseMatcher
|
128
129
|
include HaveHttpStatus
|
129
130
|
|
130
131
|
def initialize(status)
|
@@ -174,6 +175,7 @@ module RSpec
|
|
174
175
|
# @return [Symbol] representing the actual http numeric code
|
175
176
|
def actual_status
|
176
177
|
return unless actual
|
178
|
+
|
177
179
|
@actual_status ||= compute_status_from(actual)
|
178
180
|
end
|
179
181
|
|
@@ -234,33 +236,38 @@ module RSpec
|
|
234
236
|
# expect(response).to have_http_status(:missing)
|
235
237
|
# expect(response).to have_http_status(:redirect)
|
236
238
|
#
|
237
|
-
# @see RSpec::Rails::Matchers
|
238
|
-
# @see ActionDispatch::TestResponse
|
239
|
-
class GenericStatus < RSpec::Matchers::
|
239
|
+
# @see RSpec::Rails::Matchers#have_http_status
|
240
|
+
# @see https://github.com/rails/rails/blob/6-0-stable/actionpack/lib/action_dispatch/testing/test_response.rb `ActionDispatch::TestResponse`
|
241
|
+
class GenericStatus < RSpec::Rails::Matchers::BaseMatcher
|
240
242
|
include HaveHttpStatus
|
241
243
|
|
242
244
|
# @return [Array<Symbol>] of status codes which represent a HTTP status
|
243
245
|
# code "group"
|
244
246
|
# @see https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/testing/test_response.rb `ActionDispatch::TestResponse`
|
245
247
|
def self.valid_statuses
|
246
|
-
[
|
248
|
+
[
|
249
|
+
:error, :success, :missing,
|
250
|
+
:server_error, :successful, :not_found,
|
251
|
+
:redirect
|
252
|
+
]
|
247
253
|
end
|
248
254
|
|
249
255
|
def initialize(type)
|
250
256
|
unless self.class.valid_statuses.include?(type)
|
251
257
|
raise ArgumentError, "Invalid generic HTTP status: #{type.inspect}"
|
252
258
|
end
|
259
|
+
|
253
260
|
@expected = type
|
254
261
|
@actual = nil
|
255
262
|
@invalid_response = nil
|
256
263
|
end
|
257
264
|
|
258
265
|
# @return [Boolean] `true` if Rack's associated numeric HTTP code matched
|
259
|
-
# the `response` code
|
266
|
+
# the `response` code or the named response status
|
260
267
|
def matches?(response)
|
261
268
|
test_response = as_test_response(response)
|
262
269
|
@actual = test_response.response_code
|
263
|
-
test_response
|
270
|
+
check_expected_status(test_response, expected)
|
264
271
|
rescue TypeError => _ignored
|
265
272
|
@invalid_response = response
|
266
273
|
false
|
@@ -283,6 +290,19 @@ module RSpec
|
|
283
290
|
"expected the response not to have #{type_message} but it was #{actual}"
|
284
291
|
end
|
285
292
|
|
293
|
+
protected
|
294
|
+
|
295
|
+
RESPONSE_METHODS = {
|
296
|
+
success: 'successful',
|
297
|
+
error: 'server_error',
|
298
|
+
missing: 'not_found'
|
299
|
+
}.freeze
|
300
|
+
|
301
|
+
def check_expected_status(test_response, expected)
|
302
|
+
test_response.send(
|
303
|
+
"#{RESPONSE_METHODS.fetch(expected, expected)}?")
|
304
|
+
end
|
305
|
+
|
286
306
|
private
|
287
307
|
|
288
308
|
# @return [String] formating the expected status and associated code(s)
|
@@ -316,11 +336,11 @@ module RSpec
|
|
316
336
|
# @see https://github.com/rails/rails/blob/ca200378/actionpack/lib/action_dispatch/http/response.rb#L74
|
317
337
|
# @see https://github.com/rack/rack/blob/ce4a3959/lib/rack/response.rb#L119-L122
|
318
338
|
@type_codes ||= case expected
|
319
|
-
when :error
|
339
|
+
when :error, :server_error
|
320
340
|
"5xx"
|
321
|
-
when :success
|
341
|
+
when :success, :successful
|
322
342
|
"2xx"
|
323
|
-
when :missing
|
343
|
+
when :missing, :not_found
|
324
344
|
"404"
|
325
345
|
when :redirect
|
326
346
|
"3xx"
|
@@ -357,6 +377,7 @@ module RSpec
|
|
357
377
|
# @see https://github.com/rack/rack/blob/master/lib/rack/utils.rb `Rack::Utils::SYMBOL_TO_STATUS_CODE`
|
358
378
|
def have_http_status(target)
|
359
379
|
raise ArgumentError, "Invalid HTTP status: nil" unless target
|
380
|
+
|
360
381
|
HaveHttpStatus.matcher_for_status(target)
|
361
382
|
end
|
362
383
|
end
|
@@ -4,7 +4,7 @@ module RSpec
|
|
4
4
|
# Matcher for template rendering.
|
5
5
|
module RenderTemplate
|
6
6
|
# @private
|
7
|
-
class RenderTemplateMatcher < RSpec::Matchers::
|
7
|
+
class RenderTemplateMatcher < RSpec::Rails::Matchers::BaseMatcher
|
8
8
|
def initialize(scope, expected, message = nil)
|
9
9
|
@expected = Symbol === expected ? expected.to_s : expected
|
10
10
|
@message = message
|
@@ -29,6 +29,7 @@ module RSpec
|
|
29
29
|
def check_redirect
|
30
30
|
response = @scope.response
|
31
31
|
return unless response.respond_to?(:redirect?) && response.redirect?
|
32
|
+
|
32
33
|
@redirect_is = @scope.send(:normalize_argument_to_redirection, response.location)
|
33
34
|
end
|
34
35
|
|
@@ -6,7 +6,7 @@ module RSpec
|
|
6
6
|
extend RSpec::Matchers::DSL
|
7
7
|
|
8
8
|
# @private
|
9
|
-
class RouteToMatcher < RSpec::Matchers::
|
9
|
+
class RouteToMatcher < RSpec::Rails::Matchers::BaseMatcher
|
10
10
|
def initialize(scope, *expected)
|
11
11
|
@scope = scope
|
12
12
|
@expected = expected[1] || {}
|
@@ -14,7 +14,7 @@ module RSpec
|
|
14
14
|
@expected.merge!(expected[0])
|
15
15
|
else
|
16
16
|
controller, action = expected[0].split('#')
|
17
|
-
@expected.merge!(:
|
17
|
+
@expected.merge!(controller: controller, action: action)
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
@@ -26,7 +26,7 @@ module RSpec
|
|
26
26
|
path, query = *verb_to_path_map.values.first.split('?')
|
27
27
|
@scope.assert_recognizes(
|
28
28
|
@expected,
|
29
|
-
{
|
29
|
+
{method: verb_to_path_map.keys.first, path: path},
|
30
30
|
Rack::Utils.parse_nested_query(query)
|
31
31
|
)
|
32
32
|
end
|
@@ -50,20 +50,20 @@ module RSpec
|
|
50
50
|
#
|
51
51
|
# @example
|
52
52
|
#
|
53
|
-
# expect(:
|
54
|
-
# :
|
55
|
-
# :
|
53
|
+
# expect(get: "/things/special").to route_to(
|
54
|
+
# controller: "things",
|
55
|
+
# action: "special"
|
56
56
|
# )
|
57
57
|
#
|
58
|
-
# expect(:
|
58
|
+
# expect(get: "/things/special").to route_to("things#special")
|
59
59
|
#
|
60
|
-
# @see
|
60
|
+
# @see https://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html#method-i-assert_recognizes
|
61
61
|
def route_to(*expected)
|
62
62
|
RouteToMatcher.new(self, *expected)
|
63
63
|
end
|
64
64
|
|
65
65
|
# @private
|
66
|
-
class BeRoutableMatcher < RSpec::Matchers::
|
66
|
+
class BeRoutableMatcher < RSpec::Rails::Matchers::BaseMatcher
|
67
67
|
def initialize(scope)
|
68
68
|
@scope = scope
|
69
69
|
end
|
@@ -72,7 +72,7 @@ module RSpec
|
|
72
72
|
@actual = path
|
73
73
|
match_unless_raises ActionController::RoutingError do
|
74
74
|
@routing_options = @scope.routes.recognize_path(
|
75
|
-
path.values.first, :
|
75
|
+
path.values.first, method: path.keys.first
|
76
76
|
)
|
77
77
|
end
|
78
78
|
end
|
@@ -95,9 +95,9 @@ module RSpec
|
|
95
95
|
# `RouteSet#recognize_path`.
|
96
96
|
#
|
97
97
|
# @example You can use route helpers provided by rspec-rails.
|
98
|
-
# expect(:
|
99
|
-
# expect(:
|
100
|
-
# expect(:
|
98
|
+
# expect(get: "/a/path").to be_routable
|
99
|
+
# expect(post: "/another/path").to be_routable
|
100
|
+
# expect(put: "/yet/another/path").to be_routable
|
101
101
|
def be_routable
|
102
102
|
BeRoutableMatcher.new(self)
|
103
103
|
end
|
@@ -115,7 +115,7 @@ module RSpec
|
|
115
115
|
# Shorthand method for matching this type of route.
|
116
116
|
%w[get post put patch delete options head].each do |method|
|
117
117
|
define_method method do |path|
|
118
|
-
{
|
118
|
+
{method.to_sym => path}
|
119
119
|
end
|
120
120
|
end
|
121
121
|
end
|
data/lib/rspec/rails/matchers.rb
CHANGED
@@ -11,6 +11,7 @@ module RSpec
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
+
require 'rspec/rails/matchers/base_matcher'
|
14
15
|
require 'rspec/rails/matchers/have_rendered'
|
15
16
|
require 'rspec/rails/matchers/redirect_to'
|
16
17
|
require 'rspec/rails/matchers/routing_matchers'
|
@@ -19,6 +20,16 @@ require 'rspec/rails/matchers/be_a_new'
|
|
19
20
|
require 'rspec/rails/matchers/relation_match_array'
|
20
21
|
require 'rspec/rails/matchers/be_valid'
|
21
22
|
require 'rspec/rails/matchers/have_http_status'
|
23
|
+
|
22
24
|
if RSpec::Rails::FeatureCheck.has_active_job?
|
23
25
|
require 'rspec/rails/matchers/active_job'
|
26
|
+
require 'rspec/rails/matchers/have_enqueued_mail'
|
27
|
+
end
|
28
|
+
|
29
|
+
if RSpec::Rails::FeatureCheck.has_action_cable_testing?
|
30
|
+
require 'rspec/rails/matchers/action_cable'
|
31
|
+
end
|
32
|
+
|
33
|
+
if RSpec::Rails::FeatureCheck.has_action_mailbox?
|
34
|
+
require 'rspec/rails/matchers/action_mailbox'
|
24
35
|
end
|