rspec-rails 3.8.2 → 7.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/Capybara.md +5 -54
- data/Changelog.md +440 -76
- data/README.md +281 -500
- 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 +24 -7
- data/lib/generators/rspec/controller/templates/request_spec.rb +19 -0
- data/lib/generators/rspec/controller/templates/routing_spec.rb +13 -0
- data/lib/generators/rspec/feature/feature_generator.rb +3 -3
- data/lib/generators/rspec/generator/generator_generator.rb +24 -0
- data/lib/generators/rspec/generator/templates/generator_spec.rb +5 -0
- data/lib/generators/rspec/helper/helper_generator.rb +2 -2
- data/lib/generators/rspec/install/install_generator.rb +23 -6
- data/lib/generators/rspec/install/templates/spec/rails_helper.rb +32 -17
- data/lib/generators/rspec/job/job_generator.rb +2 -1
- data/lib/generators/rspec/job/templates/job_spec.rb.erb +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 +7 -4
- data/lib/generators/rspec/mailer/templates/mailer_spec.rb +2 -2
- data/lib/generators/rspec/mailer/templates/preview.rb +4 -4
- data/lib/generators/rspec/model/model_generator.rb +8 -7
- data/lib/generators/rspec/model/templates/fixtures.yml +1 -1
- data/lib/generators/rspec/request/request_generator.rb +10 -3
- data/lib/generators/rspec/scaffold/scaffold_generator.rb +36 -22
- data/lib/generators/rspec/scaffold/templates/api_controller_spec.rb +13 -49
- data/lib/generators/rspec/scaffold/templates/api_request_spec.rb +131 -0
- data/lib/generators/rspec/scaffold/templates/controller_spec.rb +14 -62
- data/lib/generators/rspec/scaffold/templates/edit_spec.rb +9 -9
- data/lib/generators/rspec/scaffold/templates/index_spec.rb +3 -2
- data/lib/generators/rspec/scaffold/templates/new_spec.rb +2 -6
- data/lib/generators/rspec/scaffold/templates/request_spec.rb +138 -0
- data/lib/generators/rspec/scaffold/templates/routing_spec.rb +8 -10
- data/lib/generators/rspec/scaffold/templates/show_spec.rb +2 -2
- data/lib/generators/rspec/system/system_generator.rb +24 -0
- data/lib/generators/rspec/system/templates/system_spec.rb +9 -0
- data/lib/generators/rspec/view/view_generator.rb +4 -4
- data/lib/generators/rspec.rb +16 -5
- data/lib/rspec/rails/adapters.rb +22 -76
- data/lib/rspec/rails/configuration.rb +112 -38
- data/lib/rspec/rails/example/channel_example_group.rb +93 -0
- data/lib/rspec/rails/example/controller_example_group.rb +5 -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 +2 -2
- data/lib/rspec/rails/example/rails_example_group.rb +7 -1
- data/lib/rspec/rails/example/request_example_group.rb +1 -4
- data/lib/rspec/rails/example/routing_example_group.rb +0 -2
- data/lib/rspec/rails/example/system_example_group.rb +88 -16
- data/lib/rspec/rails/example/view_example_group.rb +40 -28
- data/lib/rspec/rails/example.rb +2 -0
- data/lib/rspec/rails/extensions/active_record/proxy.rb +5 -10
- data/lib/rspec/rails/feature_check.rb +16 -29
- data/lib/rspec/rails/file_fixture_support.rb +11 -10
- data/lib/rspec/rails/fixture_file_upload_support.rb +20 -15
- data/lib/rspec/rails/fixture_support.rb +64 -34
- data/lib/rspec/rails/matchers/action_cable/have_broadcasted_to.rb +173 -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 +73 -0
- data/lib/rspec/rails/matchers/active_job.rb +224 -24
- 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 +258 -0
- data/lib/rspec/rails/matchers/have_http_status.rb +23 -32
- 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/relation_match_array.rb +1 -1
- data/lib/rspec/rails/matchers/routing_matchers.rb +13 -13
- data/lib/rspec/rails/matchers/send_email.rb +122 -0
- data/lib/rspec/rails/matchers.rb +12 -0
- data/lib/rspec/rails/tasks/rspec.rake +9 -17
- data/lib/rspec/rails/vendor/capybara.rb +10 -17
- data/lib/rspec/rails/version.rb +1 -1
- data/lib/rspec/rails/view_assigns.rb +0 -18
- data/lib/rspec/rails/view_path_builder.rb +1 -1
- data/lib/rspec/rails/view_rendering.rb +20 -7
- data/lib/rspec-rails.rb +36 -18
- data.tar.gz.sig +0 -0
- metadata +55 -37
- metadata.gz.sig +0 -0
- data/lib/generators/rspec/integration/integration_generator.rb +0 -22
- data/lib/generators/rspec/observer/observer_generator.rb +0 -13
- /data/lib/generators/rspec/{integration → request}/templates/request_spec.rb +0 -0
@@ -0,0 +1,258 @@
|
|
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
|
+
# rubocop: disable Metrics/ClassLength
|
8
|
+
module RSpec
|
9
|
+
module Rails
|
10
|
+
module Matchers
|
11
|
+
# Matcher class for `have_enqueued_mail`. Should not be instantiated directly.
|
12
|
+
#
|
13
|
+
# @private
|
14
|
+
# @see RSpec::Rails::Matchers#have_enqueued_mail
|
15
|
+
class HaveEnqueuedMail < ActiveJob::HaveEnqueuedJob
|
16
|
+
MAILER_JOB_METHOD = 'deliver_now'.freeze
|
17
|
+
|
18
|
+
include RSpec::Mocks::ArgumentMatchers
|
19
|
+
|
20
|
+
def initialize(mailer_class, method_name)
|
21
|
+
super(nil)
|
22
|
+
@mailer_class = mailer_class
|
23
|
+
@method_name = method_name
|
24
|
+
@mail_args = []
|
25
|
+
end
|
26
|
+
|
27
|
+
def description
|
28
|
+
"enqueues #{mailer_class_name}.#{@method_name}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def with(*args, &block)
|
32
|
+
@mail_args = args
|
33
|
+
block.nil? ? super : super(&yield_mail_args(block))
|
34
|
+
end
|
35
|
+
|
36
|
+
def matches?(block)
|
37
|
+
raise ArgumentError, 'have_enqueued_mail and enqueue_mail only work with block arguments' unless block.respond_to?(:call)
|
38
|
+
|
39
|
+
check_active_job_adapter
|
40
|
+
super
|
41
|
+
end
|
42
|
+
|
43
|
+
def failure_message
|
44
|
+
return @failure_message if defined?(@failure_message)
|
45
|
+
|
46
|
+
"expected to enqueue #{base_message}".tap do |msg|
|
47
|
+
msg << "\n#{unmatching_mail_jobs_message}" if unmatching_mail_jobs.any?
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def failure_message_when_negated
|
52
|
+
"expected not to enqueue #{base_message}"
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def base_message
|
58
|
+
[mailer_class_name, @method_name].compact.join('.').tap do |msg|
|
59
|
+
msg << " #{expected_count_message}"
|
60
|
+
msg << " with #{@mail_args}," if @mail_args.any?
|
61
|
+
msg << " on queue #{@queue}," if @queue
|
62
|
+
msg << " at #{@at.inspect}," if @at
|
63
|
+
msg << " but enqueued #{@matching_jobs.size}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def expected_count_message
|
68
|
+
"#{message_expectation_modifier} #{@expected_number} #{@expected_number == 1 ? 'time' : 'times'}"
|
69
|
+
end
|
70
|
+
|
71
|
+
def mailer_class_name
|
72
|
+
@mailer_class ? @mailer_class.name : 'ActionMailer::Base'
|
73
|
+
end
|
74
|
+
|
75
|
+
def job_match?(job)
|
76
|
+
legacy_mail?(job) || parameterized_mail?(job) || unified_mail?(job)
|
77
|
+
end
|
78
|
+
|
79
|
+
def arguments_match?(job)
|
80
|
+
@args =
|
81
|
+
if @mail_args.any?
|
82
|
+
base_mailer_args + @mail_args
|
83
|
+
elsif @mailer_class && @method_name
|
84
|
+
base_mailer_args + [any_args]
|
85
|
+
elsif @mailer_class
|
86
|
+
[mailer_class_name, any_args]
|
87
|
+
else
|
88
|
+
[]
|
89
|
+
end
|
90
|
+
|
91
|
+
super(job)
|
92
|
+
end
|
93
|
+
|
94
|
+
def detect_args_signature_mismatch(jobs)
|
95
|
+
return if @method_name.nil?
|
96
|
+
|
97
|
+
mailer_class = mailer_class_name.constantize
|
98
|
+
|
99
|
+
jobs.each do |job|
|
100
|
+
mailer_args = extract_args_without_parameterized_params(job)
|
101
|
+
|
102
|
+
if (signature_mismatch = check_args_signature_mismatch(mailer_class, @method_name, mailer_args))
|
103
|
+
return signature_mismatch
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
nil
|
108
|
+
end
|
109
|
+
|
110
|
+
def base_mailer_args
|
111
|
+
[mailer_class_name, @method_name.to_s, MAILER_JOB_METHOD]
|
112
|
+
end
|
113
|
+
|
114
|
+
def yield_mail_args(block)
|
115
|
+
proc { |*job_args| block.call(*(job_args - base_mailer_args)) }
|
116
|
+
end
|
117
|
+
|
118
|
+
def check_active_job_adapter
|
119
|
+
return if ::ActiveJob::QueueAdapters::TestAdapter === ::ActiveJob::Base.queue_adapter
|
120
|
+
|
121
|
+
raise StandardError, "To use HaveEnqueuedMail matcher set `ActiveJob::Base.queue_adapter = :test`"
|
122
|
+
end
|
123
|
+
|
124
|
+
def unmatching_mail_jobs
|
125
|
+
@unmatching_jobs.select do |job|
|
126
|
+
job_match?(job)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def unmatching_mail_jobs_message
|
131
|
+
msg = "Queued deliveries:"
|
132
|
+
|
133
|
+
unmatching_mail_jobs.each do |job|
|
134
|
+
msg << "\n #{mail_job_message(job)}"
|
135
|
+
end
|
136
|
+
|
137
|
+
msg
|
138
|
+
end
|
139
|
+
|
140
|
+
def mail_job_message(job)
|
141
|
+
job_args = deserialize_arguments(job)
|
142
|
+
|
143
|
+
mailer_method = job_args[0..1].join('.')
|
144
|
+
mailer_args = job_args[3..-1]
|
145
|
+
|
146
|
+
msg_parts = []
|
147
|
+
msg_parts << "with #{mailer_args}" if mailer_args.any?
|
148
|
+
msg_parts << "on queue #{job[:queue]}" if job[:queue] && job[:queue] != 'mailers'
|
149
|
+
msg_parts << "at #{Time.at(job[:at])}" if job[:at]
|
150
|
+
|
151
|
+
"#{mailer_method} #{msg_parts.join(', ')}".strip
|
152
|
+
end
|
153
|
+
|
154
|
+
# Ruby 3.1 changed how params were serialized on Rails 6.1
|
155
|
+
# so we override the active job implementation and customize it here.
|
156
|
+
def deserialize_arguments(job)
|
157
|
+
args = super
|
158
|
+
|
159
|
+
return args unless Hash === args.last
|
160
|
+
|
161
|
+
hash = args.pop
|
162
|
+
|
163
|
+
if hash.key?("_aj_ruby2_keywords")
|
164
|
+
keywords = hash["_aj_ruby2_keywords"]
|
165
|
+
|
166
|
+
original_hash = keywords.each_with_object({}) { |keyword, new_hash| new_hash[keyword.to_sym] = hash[keyword] }
|
167
|
+
|
168
|
+
args + [original_hash]
|
169
|
+
elsif hash.key?(:args) && hash.key?(:params)
|
170
|
+
args + [hash]
|
171
|
+
elsif hash.key?(:args)
|
172
|
+
args + hash[:args]
|
173
|
+
else
|
174
|
+
args + [hash]
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def extract_args_without_parameterized_params(job)
|
179
|
+
args = deserialize_arguments(job)
|
180
|
+
mailer_args = args - base_mailer_args
|
181
|
+
|
182
|
+
if parameterized_mail?(job)
|
183
|
+
mailer_args = mailer_args[1..-1] # ignore parameterized params
|
184
|
+
elsif mailer_args.last.is_a?(Hash) && mailer_args.last.key?(:args)
|
185
|
+
mailer_args = args.last[:args]
|
186
|
+
end
|
187
|
+
|
188
|
+
mailer_args
|
189
|
+
end
|
190
|
+
|
191
|
+
def legacy_mail?(job)
|
192
|
+
RSpec::Rails::FeatureCheck.has_action_mailer_legacy_delivery_job? && job[:job] <= ActionMailer::DeliveryJob
|
193
|
+
end
|
194
|
+
|
195
|
+
def parameterized_mail?(job)
|
196
|
+
RSpec::Rails::FeatureCheck.has_action_mailer_parameterized? && job[:job] <= ActionMailer::Parameterized::DeliveryJob
|
197
|
+
end
|
198
|
+
|
199
|
+
def unified_mail?(job)
|
200
|
+
RSpec::Rails::FeatureCheck.has_action_mailer_unified_delivery? && job[:job] <= ActionMailer::MailDeliveryJob
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# @api public
|
205
|
+
# Passes if an email has been enqueued inside block.
|
206
|
+
# May chain with to specify expected arguments.
|
207
|
+
# May chain at_least, at_most or exactly to specify a number of times.
|
208
|
+
# May chain at to specify a send time.
|
209
|
+
# May chain on_queue to specify a queue.
|
210
|
+
#
|
211
|
+
# @example
|
212
|
+
# expect {
|
213
|
+
# MyMailer.welcome(user).deliver_later
|
214
|
+
# }.to have_enqueued_mail
|
215
|
+
#
|
216
|
+
# expect {
|
217
|
+
# MyMailer.welcome(user).deliver_later
|
218
|
+
# }.to have_enqueued_mail(MyMailer)
|
219
|
+
#
|
220
|
+
# expect {
|
221
|
+
# MyMailer.welcome(user).deliver_later
|
222
|
+
# }.to have_enqueued_mail(MyMailer, :welcome)
|
223
|
+
#
|
224
|
+
# # Using alias
|
225
|
+
# expect {
|
226
|
+
# MyMailer.welcome(user).deliver_later
|
227
|
+
# }.to enqueue_mail(MyMailer, :welcome)
|
228
|
+
#
|
229
|
+
# expect {
|
230
|
+
# MyMailer.welcome(user).deliver_later
|
231
|
+
# }.to have_enqueued_mail(MyMailer, :welcome).with(user)
|
232
|
+
#
|
233
|
+
# expect {
|
234
|
+
# MyMailer.welcome(user).deliver_later
|
235
|
+
# MyMailer.welcome(user).deliver_later
|
236
|
+
# }.to have_enqueued_mail(MyMailer, :welcome).at_least(:once)
|
237
|
+
#
|
238
|
+
# expect {
|
239
|
+
# MyMailer.welcome(user).deliver_later
|
240
|
+
# }.to have_enqueued_mail(MyMailer, :welcome).at_most(:twice)
|
241
|
+
#
|
242
|
+
# expect {
|
243
|
+
# MyMailer.welcome(user).deliver_later(wait_until: Date.tomorrow.noon)
|
244
|
+
# }.to have_enqueued_mail(MyMailer, :welcome).at(Date.tomorrow.noon)
|
245
|
+
#
|
246
|
+
# expect {
|
247
|
+
# MyMailer.welcome(user).deliver_later(queue: :urgent_mail)
|
248
|
+
# }.to have_enqueued_mail(MyMailer, :welcome).on_queue(:urgent_mail)
|
249
|
+
def have_enqueued_mail(mailer_class = nil, mail_method_name = nil)
|
250
|
+
HaveEnqueuedMail.new(mailer_class, mail_method_name)
|
251
|
+
end
|
252
|
+
alias_method :have_enqueued_email, :have_enqueued_mail
|
253
|
+
alias_method :enqueue_mail, :have_enqueued_mail
|
254
|
+
alias_method :enqueue_email, :have_enqueued_mail
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
# rubocop: enable Metrics/ClassLength
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# The following code inspired and modified from Rails' `assert_response`:
|
2
2
|
#
|
3
|
-
# https://github.com/rails/rails/blob/
|
3
|
+
# https://github.com/rails/rails/blob/main/actionpack/lib/action_dispatch/testing/assertions/response.rb#L22-L38
|
4
4
|
#
|
5
5
|
# Thank you to all the Rails devs who did the heavy lifting on this!
|
6
6
|
|
@@ -33,7 +33,7 @@ module RSpec
|
|
33
33
|
# @param obj [Object] object to convert to a response
|
34
34
|
# @return [ActionDispatch::TestResponse]
|
35
35
|
def as_test_response(obj)
|
36
|
-
if ::ActionDispatch::Response === obj
|
36
|
+
if ::ActionDispatch::Response === obj || ::Rack::MockResponse === obj
|
37
37
|
::ActionDispatch::TestResponse.from_response(obj)
|
38
38
|
elsif ::ActionDispatch::TestResponse === obj
|
39
39
|
obj
|
@@ -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
|
|
@@ -214,11 +216,7 @@ module RSpec
|
|
214
216
|
# @see Rack::Utils::SYMBOL_TO_STATUS_CODE
|
215
217
|
# @raise [ArgumentError] if an associated code could not be found
|
216
218
|
def set_expected_code!
|
217
|
-
@expected ||=
|
218
|
-
Rack::Utils::SYMBOL_TO_STATUS_CODE.fetch(expected_status) do
|
219
|
-
raise ArgumentError,
|
220
|
-
"Invalid HTTP status: #{expected_status.inspect}"
|
221
|
-
end
|
219
|
+
@expected ||= Rack::Utils.status_code(expected_status)
|
222
220
|
end
|
223
221
|
end
|
224
222
|
|
@@ -234,14 +232,14 @@ module RSpec
|
|
234
232
|
# expect(response).to have_http_status(:missing)
|
235
233
|
# expect(response).to have_http_status(:redirect)
|
236
234
|
#
|
237
|
-
# @see RSpec::Rails::Matchers
|
238
|
-
# @see ActionDispatch::TestResponse
|
239
|
-
class GenericStatus < RSpec::Matchers::
|
235
|
+
# @see RSpec::Rails::Matchers#have_http_status
|
236
|
+
# @see https://github.com/rails/rails/blob/7-2-stable/actionpack/lib/action_dispatch/testing/test_response.rb `ActionDispatch::TestResponse`
|
237
|
+
class GenericStatus < RSpec::Rails::Matchers::BaseMatcher
|
240
238
|
include HaveHttpStatus
|
241
239
|
|
242
240
|
# @return [Array<Symbol>] of status codes which represent a HTTP status
|
243
241
|
# code "group"
|
244
|
-
# @see https://github.com/rails/rails/blob/
|
242
|
+
# @see https://github.com/rails/rails/blob/main/actionpack/lib/action_dispatch/testing/test_response.rb `ActionDispatch::TestResponse`
|
245
243
|
def self.valid_statuses
|
246
244
|
[
|
247
245
|
:error, :success, :missing,
|
@@ -254,6 +252,7 @@ module RSpec
|
|
254
252
|
unless self.class.valid_statuses.include?(type)
|
255
253
|
raise ArgumentError, "Invalid generic HTTP status: #{type.inspect}"
|
256
254
|
end
|
255
|
+
|
257
256
|
@expected = type
|
258
257
|
@actual = nil
|
259
258
|
@invalid_response = nil
|
@@ -289,20 +288,11 @@ module RSpec
|
|
289
288
|
|
290
289
|
protected
|
291
290
|
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
:missing => 'not_found'
|
298
|
-
}.freeze
|
299
|
-
else
|
300
|
-
RESPONSE_METHODS = {
|
301
|
-
:successful => 'success',
|
302
|
-
:server_error => 'error',
|
303
|
-
:not_found => 'missing'
|
304
|
-
}.freeze
|
305
|
-
end
|
291
|
+
RESPONSE_METHODS = {
|
292
|
+
success: 'successful',
|
293
|
+
error: 'server_error',
|
294
|
+
missing: 'not_found'
|
295
|
+
}.freeze
|
306
296
|
|
307
297
|
def check_expected_status(test_response, expected)
|
308
298
|
test_response.send(
|
@@ -311,7 +301,7 @@ module RSpec
|
|
311
301
|
|
312
302
|
private
|
313
303
|
|
314
|
-
# @return [String]
|
304
|
+
# @return [String] formatting the expected status and associated code(s)
|
315
305
|
def type_message
|
316
306
|
@type_message ||= (expected == :error ? "an error" : "a #{expected}") +
|
317
307
|
" status code (#{type_codes})"
|
@@ -319,7 +309,7 @@ module RSpec
|
|
319
309
|
|
320
310
|
# @return [String] formatting the associated code(s) for the various
|
321
311
|
# status code "groups"
|
322
|
-
# @see https://github.com/rails/rails/blob/
|
312
|
+
# @see https://github.com/rails/rails/blob/main/actionpack/lib/action_dispatch/testing/test_response.rb `ActionDispatch::TestResponse`
|
323
313
|
# @see https://github.com/rack/rack/blob/master/lib/rack/response.rb `Rack::Response`
|
324
314
|
def type_codes
|
325
315
|
# At the time of this commit the most recent version of
|
@@ -379,10 +369,11 @@ module RSpec
|
|
379
369
|
# expect(response).to have_http_status(404)
|
380
370
|
# expect(page).to have_http_status(:created)
|
381
371
|
#
|
382
|
-
# @see https://github.com/rails/rails/blob/
|
372
|
+
# @see https://github.com/rails/rails/blob/main/actionpack/lib/action_dispatch/testing/test_response.rb `ActionDispatch::TestResponse`
|
383
373
|
# @see https://github.com/rack/rack/blob/master/lib/rack/utils.rb `Rack::Utils::SYMBOL_TO_STATUS_CODE`
|
384
374
|
def have_http_status(target)
|
385
375
|
raise ArgumentError, "Invalid HTTP status: nil" unless target
|
376
|
+
|
386
377
|
HaveHttpStatus.matcher_for_status(target)
|
387
378
|
end
|
388
379
|
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
|
|
@@ -1,3 +1,3 @@
|
|
1
|
-
if defined?(ActiveRecord::Relation)
|
1
|
+
if defined?(ActiveRecord::Relation) && defined?(RSpec::Matchers::BuiltIn::OperatorMatcher) # RSpec 4 removed OperatorMatcher
|
2
2
|
RSpec::Matchers::BuiltIn::OperatorMatcher.register(ActiveRecord::Relation, '=~', RSpec::Matchers::BuiltIn::ContainExactly)
|
3
3
|
end
|
@@ -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
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSpec
|
4
|
+
module Rails
|
5
|
+
module Matchers
|
6
|
+
# @api private
|
7
|
+
#
|
8
|
+
# Matcher class for `send_email`. Should not be instantiated directly.
|
9
|
+
#
|
10
|
+
# @see RSpec::Rails::Matchers#send_email
|
11
|
+
class SendEmail < RSpec::Rails::Matchers::BaseMatcher
|
12
|
+
# @api private
|
13
|
+
# Define the email attributes that should be included in the inspection output.
|
14
|
+
INSPECT_EMAIL_ATTRIBUTES = %i[subject from to cc bcc].freeze
|
15
|
+
|
16
|
+
def initialize(criteria)
|
17
|
+
@criteria = criteria
|
18
|
+
end
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
def supports_value_expectations?
|
22
|
+
false
|
23
|
+
end
|
24
|
+
|
25
|
+
# @api private
|
26
|
+
def supports_block_expectations?
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
def matches?(block)
|
31
|
+
define_matched_emails(block)
|
32
|
+
|
33
|
+
@matched_emails.one?
|
34
|
+
end
|
35
|
+
|
36
|
+
# @api private
|
37
|
+
# @return [String]
|
38
|
+
def failure_message
|
39
|
+
result =
|
40
|
+
if multiple_match?
|
41
|
+
"More than 1 matching emails were sent."
|
42
|
+
else
|
43
|
+
"No matching emails were sent."
|
44
|
+
end
|
45
|
+
"#{result}#{sent_emails_message}"
|
46
|
+
end
|
47
|
+
|
48
|
+
# @api private
|
49
|
+
# @return [String]
|
50
|
+
def failure_message_when_negated
|
51
|
+
"Expected not to send an email but it was sent."
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def diffable?
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
def deliveries
|
61
|
+
ActionMailer::Base.deliveries
|
62
|
+
end
|
63
|
+
|
64
|
+
def define_matched_emails(block)
|
65
|
+
before = deliveries.dup
|
66
|
+
|
67
|
+
block.call
|
68
|
+
|
69
|
+
after = deliveries
|
70
|
+
|
71
|
+
@diff = after - before
|
72
|
+
@matched_emails = @diff.select(&method(:matched_email?))
|
73
|
+
end
|
74
|
+
|
75
|
+
def matched_email?(email)
|
76
|
+
@criteria.all? do |attr, value|
|
77
|
+
expected =
|
78
|
+
case attr
|
79
|
+
when :to, :from, :cc, :bcc then Array(value)
|
80
|
+
else
|
81
|
+
value
|
82
|
+
end
|
83
|
+
|
84
|
+
values_match?(expected, email.public_send(attr))
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def multiple_match?
|
89
|
+
@matched_emails.many?
|
90
|
+
end
|
91
|
+
|
92
|
+
def sent_emails_message
|
93
|
+
if @diff.empty?
|
94
|
+
"\n\nThere were no any emails sent inside the expectation block."
|
95
|
+
else
|
96
|
+
sent_emails =
|
97
|
+
@diff.map do |email|
|
98
|
+
inspected = INSPECT_EMAIL_ATTRIBUTES.map { |attr| "#{attr}: #{email.public_send(attr)}" }.join(", ")
|
99
|
+
"- #{inspected}"
|
100
|
+
end.join("\n")
|
101
|
+
"\n\nThe following emails were sent:\n#{sent_emails}"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# @api public
|
107
|
+
# Check email sending with specific parameters.
|
108
|
+
#
|
109
|
+
# @example Positive expectation
|
110
|
+
# expect { action }.to send_email
|
111
|
+
#
|
112
|
+
# @example Negative expectations
|
113
|
+
# expect { action }.not_to send_email
|
114
|
+
#
|
115
|
+
# @example More precise expectation with attributes to match
|
116
|
+
# expect { action }.to send_email(to: 'test@example.com', subject: 'Confirm email')
|
117
|
+
def send_email(criteria = {})
|
118
|
+
SendEmail.new(criteria)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
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,17 @@ 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
|
+
require 'rspec/rails/matchers/send_email'
|
24
|
+
|
22
25
|
if RSpec::Rails::FeatureCheck.has_active_job?
|
23
26
|
require 'rspec/rails/matchers/active_job'
|
27
|
+
require 'rspec/rails/matchers/have_enqueued_mail'
|
28
|
+
end
|
29
|
+
|
30
|
+
if RSpec::Rails::FeatureCheck.has_action_cable_testing?
|
31
|
+
require 'rspec/rails/matchers/action_cable'
|
32
|
+
end
|
33
|
+
|
34
|
+
if RSpec::Rails::FeatureCheck.has_action_mailbox?
|
35
|
+
require 'rspec/rails/matchers/action_mailbox'
|
24
36
|
end
|