forked from rspec/rspec-rails
/
have_enqueued_mail.rb
198 lines (168 loc) · 6.37 KB
/
have_enqueued_mail.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# We require the minimum amount of rspec-mocks possible to avoid
# conflicts with other mocking frameworks.
# See: https://github.com/rspec/rspec-rails/issues/2252
require "rspec/mocks/argument_matchers"
require "rspec/rails/matchers/active_job"
module RSpec
module Rails
module Matchers
# Matcher class for `have_enqueued_mail`. Should not be instantiated directly.
#
# @private
# @see RSpec::Rails::Matchers#have_enqueued_mail
class HaveEnqueuedMail < ActiveJob::HaveEnqueuedJob
MAILER_JOB_METHOD = 'deliver_now'.freeze
include RSpec::Mocks::ArgumentMatchers
def initialize(mailer_class, method_name)
super(nil)
@mailer_class = mailer_class
@method_name = method_name
@mail_args = []
end
def description
"enqueues #{mailer_class_name}.#{@method_name}"
end
def with(*args, &block)
@mail_args = args
block.nil? ? super : super(&yield_mail_args(block))
end
def matches?(block)
raise ArgumentError, 'have_enqueued_mail and enqueue_mail only work with block arguments' unless block.respond_to?(:call)
check_active_job_adapter
super
end
def failure_message
"expected to enqueue #{base_message}".tap do |msg|
msg << "\n#{unmatching_mail_jobs_message}" if unmatching_mail_jobs.any?
end
end
def failure_message_when_negated
"expected not to enqueue #{base_message}"
end
private
def base_message
[mailer_class_name, @method_name].compact.join('.').tap do |msg|
msg << " #{expected_count_message}"
msg << " with #{@mail_args}," if @mail_args.any?
msg << " on queue #{@queue}," if @queue
msg << " at #{@at.inspect}," if @at
msg << " but enqueued #{@matching_jobs.size}"
end
end
def expected_count_message
"#{message_expectation_modifier} #{@expected_number} #{@expected_number == 1 ? 'time' : 'times'}"
end
def mailer_class_name
@mailer_class ? @mailer_class.name : 'ActionMailer::Base'
end
def job_match?(job)
legacy_mail?(job) || parameterized_mail?(job) || unified_mail?(job)
end
def arguments_match?(job)
@args =
if @mail_args.any?
base_mailer_args + @mail_args
elsif @mailer_class && @method_name
base_mailer_args + [any_args]
elsif @mailer_class
[mailer_class_name, any_args]
else
[]
end
super(job)
end
def base_mailer_args
[mailer_class_name, @method_name.to_s, MAILER_JOB_METHOD]
end
def yield_mail_args(block)
proc { |*job_args| block.call(*(job_args - base_mailer_args)) }
end
def check_active_job_adapter
return if ::ActiveJob::QueueAdapters::TestAdapter === ::ActiveJob::Base.queue_adapter
raise StandardError, "To use HaveEnqueuedMail matcher set `ActiveJob::Base.queue_adapter = :test`"
end
def unmatching_mail_jobs
@unmatching_jobs.select do |job|
job_match?(job)
end
end
def unmatching_mail_jobs_message
msg = "Queued deliveries:"
unmatching_mail_jobs.each do |job|
msg << "\n #{mail_job_message(job)}"
end
msg
end
def mail_job_message(job)
mailer_method = job[:args][0..1].join('.')
mailer_args = job[:args][3..-1]
msg_parts = []
msg_parts << "with #{mailer_args}" if mailer_args.any?
msg_parts << "on queue #{job[:queue]}" if job[:queue] && job[:queue] != 'mailers'
msg_parts << "at #{Time.at(job[:at])}" if job[:at]
"#{mailer_method} #{msg_parts.join(', ')}".strip
end
def legacy_mail?(job)
job[:job] <= ActionMailer::DeliveryJob
end
def parameterized_mail?(job)
RSpec::Rails::FeatureCheck.has_action_mailer_parameterized? && job[:job] <= ActionMailer::Parameterized::DeliveryJob
end
def unified_mail?(job)
RSpec::Rails::FeatureCheck.has_action_mailer_unified_delivery? && job[:job] <= ActionMailer::MailDeliveryJob
end
end
# @api public
# Passes if an email has been enqueued inside block.
# May chain with to specify expected arguments.
# May chain at_least, at_most or exactly to specify a number of times.
# May chain at to specify a send time.
# May chain on_queue to specify a queue.
#
# @example
# expect {
# MyMailer.welcome(user).deliver_later
# }.to have_enqueued_mail
#
# expect {
# MyMailer.welcome(user).deliver_later
# }.to have_enqueued_mail(MyMailer)
#
# expect {
# MyMailer.welcome(user).deliver_later
# }.to have_enqueued_mail(MyMailer, :welcome)
#
# # Using alias
# expect {
# MyMailer.welcome(user).deliver_later
# }.to enqueue_mail(MyMailer, :welcome)
#
# expect {
# MyMailer.welcome(user).deliver_later
# }.to have_enqueued_mail(MyMailer, :welcome).with(user)
#
# expect {
# MyMailer.welcome(user).deliver_later
# MyMailer.welcome(user).deliver_later
# }.to have_enqueued_mail(MyMailer, :welcome).at_least(:once)
#
# expect {
# MyMailer.welcome(user).deliver_later
# }.to have_enqueued_mail(MyMailer, :welcome).at_most(:twice)
#
# expect {
# MyMailer.welcome(user).deliver_later(wait_until: Date.tomorrow.noon)
# }.to have_enqueued_mail(MyMailer, :welcome).at(Date.tomorrow.noon)
#
# expect {
# MyMailer.welcome(user).deliver_later(queue: :urgent_mail)
# }.to have_enqueued_mail(MyMailer, :welcome).on_queue(:urgent_mail)
def have_enqueued_mail(mailer_class = nil, mail_method_name = nil)
HaveEnqueuedMail.new(mailer_class, mail_method_name)
end
alias_method :have_enqueued_email, :have_enqueued_mail
alias_method :enqueue_mail, :have_enqueued_mail
alias_method :enqueue_email, :have_enqueued_mail
end
end
end