Skip to content

Commit 8479103

Browse files
author
Joel Lubrano
committed
Add support for at and on_queue
deliver_later can be called with `wait`, `wait_until`, and `queue` keyword arguments, so the have_enqueued_mail matcher should support matching those types of things. At this point, I am beginning to suspect that this code would be better off using inheritance over composition. I am going to try and refactor to inherit from the ActiveJob base matcher now that I have all of the tests in a good state.
1 parent fd9d4d5 commit 8479103

File tree

2 files changed

+89
-13
lines changed

2 files changed

+89
-13
lines changed

lib/rspec/rails/matchers/have_enqueued_mail.rb

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ def initialize(mailer_class, method_name)
1414
@mailer_class = mailer_class
1515
@method_name = method_name
1616
@args = []
17+
@at = nil
18+
@queue = nil
1719
@job_matcher = ActiveJob::HaveEnqueuedJob.new(ActionMailer::DeliveryJob)
1820
set_expected_count(:exactly, 1)
1921
end
@@ -35,6 +37,18 @@ def with(*args)
3537
self
3638
end
3739

40+
def at(send_time)
41+
@at = send_time
42+
@job_matcher.at(send_time)
43+
self
44+
end
45+
46+
def on_queue(queue)
47+
@queue = queue
48+
@job_matcher.on_queue(queue)
49+
self
50+
end
51+
3852
%i[exactly at_least at_most].each do |method|
3953
define_method(method) do |count|
4054
@job_matcher.public_send(method, count)
@@ -73,8 +87,10 @@ def supports_block_expectations?
7387
def base_message
7488
"#{@mailer_class.name}.#{@method_name}".tap do |msg|
7589
msg << " #{expected_count_message}"
76-
msg << " with #{@args}" if @args.any?
77-
msg << ", but enqueued #{@job_matcher.matching_jobs.size}"
90+
msg << " with #{@args}," if @args.any?
91+
msg << " on queue #{@queue}," if @queue
92+
msg << " at #{@at.inspect}," if @at
93+
msg << " but enqueued #{@job_matcher.matching_jobs.size}"
7894
end
7995
end
8096

@@ -125,19 +141,31 @@ def unmatching_mail_jobs_message
125141
msg = "Queued deliveries:"
126142

127143
unmatching_mail_jobs.each do |job|
128-
mailer_method = job[:args][0..1].join('.')
129-
mailer_args = job[:args][3..-1]
130-
131-
msg << "\n #{mailer_method}"
132-
msg << " with #{mailer_args}" if mailer_args.any?
144+
msg << "\n #{mail_job_message(job)}"
133145
end
134146

135147
msg
136148
end
149+
150+
def mail_job_message(job)
151+
mailer_method = job[:args][0..1].join('.')
152+
153+
mailer_args = job[:args][3..-1]
154+
msg_parts = []
155+
msg_parts << "with #{mailer_args}" if mailer_args.any?
156+
msg_parts << "on queue #{job[:queue]}" if job[:queue] && job[:queue] != 'mailers'
157+
msg_parts << "at #{Time.at(job[:at])}" if job[:at]
158+
159+
"#{mailer_method} #{msg_parts.join(', ')}".strip
160+
end
137161
end
138162

139163
# @api public
140-
# Passes if an email has been enqueued inside block. May chain with to specify expected arguments.
164+
# Passes if an email has been enqueued inside block.
165+
# May chain with to specify expected arguments.
166+
# May chain at_least, at_most or exactly to specify a number of times.
167+
# May chain at to specify a send time.
168+
# May chain on_queue to specify a queue.
141169
#
142170
# @example
143171
# expect {
@@ -152,6 +180,23 @@ def unmatching_mail_jobs_message
152180
# expect {
153181
# MyMailer.welcome(user).deliver_later
154182
# }.to have_enqueued_mail(MyMailer, :welcome).with(user)
183+
#
184+
# expect {
185+
# MyMailer.welcome(user).deliver_later
186+
# MyMailer.welcome(user).deliver_later
187+
# }.to have_enqueued_mail(MyMailer, :welcome).at_least(:once)
188+
#
189+
# expect {
190+
# MyMailer.welcome(user).deliver_later
191+
# }.to have_enqueued_mail(MyMailer, :welcome).at_most(:twice)
192+
#
193+
# expect {
194+
# MyMailer.welcome(user).deliver_later(wait_until: Date.tomorrow.noon)
195+
# }.to have_enqueued_mail(MyMailer, :welcome).at(Date.tomorrow.noon)
196+
#
197+
# expect {
198+
# MyMailer.welcome(user).deliver_later(queue: :urgent_mail)
199+
# }.to have_enqueued_mail(MyMailer, :welcome).on_queue(:urgent_mail)
155200
def have_enqueued_mail(mailer_class, mail_method_name)
156201
check_active_job_adapter
157202
HaveEnqueuedMail.new(mailer_class, mail_method_name)

spec/rspec/rails/matchers/have_enqueued_mail_spec.rb

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def email_with_optional_args(required_arg, optional_arg = nil); end
8989
expect {
9090
TestMailer.test_email.deliver_later
9191
}.not_to have_enqueued_mail(TestMailer, :test_email)
92-
}.to raise_error(/expected not to enqueue TestMailer.test_email exactly 1 time, but enqueued 1/)
92+
}.to raise_error(/expected not to enqueue TestMailer.test_email exactly 1 time but enqueued 1/)
9393
end
9494

9595
it "passes with :once count" do
@@ -129,7 +129,7 @@ def email_with_optional_args(required_arg, optional_arg = nil); end
129129
it "generates a failure message with at least hint" do
130130
expect {
131131
expect { }.to have_enqueued_mail(TestMailer, :test_email).at_least(:once)
132-
}.to raise_error(/expected to enqueue TestMailer.test_email at least 1 time, but enqueued 0/)
132+
}.to raise_error(/expected to enqueue TestMailer.test_email at least 1 time but enqueued 0/)
133133
end
134134

135135
it "generates a failure message with at most hint" do
@@ -138,7 +138,7 @@ def email_with_optional_args(required_arg, optional_arg = nil); end
138138
TestMailer.test_email.deliver_later
139139
TestMailer.test_email.deliver_later
140140
}.to have_enqueued_mail(TestMailer, :test_email).at_most(:once)
141-
}.to raise_error(/expected to enqueue TestMailer.test_email at most 1 time, but enqueued 2/)
141+
}.to raise_error(/expected to enqueue TestMailer.test_email at most 1 time but enqueued 2/)
142142
end
143143

144144
it "passes for mailer methods that accept arguments when the provided argument matcher is not used" do
@@ -183,16 +183,47 @@ def email_with_optional_args(required_arg, optional_arg = nil); end
183183
}.to raise_error(/expected to enqueue TestMailer.email_with_args exactly 1 time with \[1, 2\], but enqueued 0/)
184184
end
185185

186+
it "passes when deliver_later is called with a wait_until argument" do
187+
send_time = Date.tomorrow.noon
188+
189+
expect { TestMailer.test_email.deliver_later(wait_until: send_time) }
190+
.to have_enqueued_email(TestMailer, :test_email).at(send_time)
191+
end
192+
193+
it "generates a failure message with at" do
194+
send_time = Date.tomorrow.noon
195+
196+
expect {
197+
expect { TestMailer.test_email.deliver_later(wait_until: send_time + 1) }
198+
.to have_enqueued_email(TestMailer, :test_email).at(send_time)
199+
}.to raise_error(/expected to enqueue TestMailer.test_email exactly 1 time at #{send_time.inspect}/)
200+
end
201+
202+
it "passes when deliver_later is called with a queue argument" do
203+
expect { TestMailer.test_email.deliver_later(queue: 'urgent_mail') }
204+
.to have_enqueued_email(TestMailer, :test_email).on_queue('urgent_mail')
205+
end
206+
207+
it "generates a failure message with on_queue" do
208+
expect {
209+
expect { TestMailer.test_email.deliver_later(queue: 'not_urgent_mail') }
210+
.to have_enqueued_email(TestMailer, :test_email).on_queue('urgent_mail')
211+
}.to raise_error(/expected to enqueue TestMailer.test_email exactly 1 time on queue urgent_mail/)
212+
end
213+
186214
it "generates a failure message with unmatching enqueued mail jobs" do
215+
send_time = Date.tomorrow.noon
216+
queue = 'urgent_mail'
217+
187218
message = "expected to enqueue TestMailer.email_with_args exactly 1 time with [1, 2], but enqueued 0" + \
188219
"\nQueued deliveries:" + \
189220
"\n TestMailer.test_email" + \
190-
"\n TestMailer.email_with_args with [3, 4]"
221+
"\n TestMailer.email_with_args with [3, 4], on queue #{queue}, at #{send_time}"
191222

192223
expect {
193224
expect {
194225
TestMailer.test_email.deliver_later
195-
TestMailer.email_with_args(3, 4).deliver_later
226+
TestMailer.email_with_args(3, 4).deliver_later(wait_until: send_time, queue: queue)
196227
}.to have_enqueued_email(TestMailer, :email_with_args).with(1, 2)
197228
}.to raise_error(message)
198229
end

0 commit comments

Comments
 (0)