-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Feature/have enqueued mail matcher #2047
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
JonRowe
merged 25 commits into
rspec:master
from
jdlubrano:feature/have-enqueued-mail-matcher
Jan 11, 2019
Merged
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
5de5efa
Write first test case for calling simple mailer method with deliver_l…
7e7a218
Move deliver_later gem logic into HaveEnqueuedMail matcher
1692d1d
Add tests for arguments matcher and error messages. Largely based on…
003b3fa
Add exactly, at_least, at_most functionality for have_enqueued_mail m…
0af1edd
Support mailer methods with default/optional arguments; Support match…
cd61a4d
Correct private YARD doc on RSpec::Rails::Matchers::HaveEnqueuedJob
63e9caf
Provide unmatching enqueued email info in failure messages
cd7ad09
Add tests for have_enqueued_mail matcher at_least and at_most modifiers
765a9fe
Add support for `at` and `on_queue`
0f6eb1a
Explicitly require rspec/mocks for access to `anything` method
e2df63a
Ignore Rubocop class length for HaveEnqueuedMail class
8a29d96
Replace .negative? with < 0 for older ruby versions
5e9263f
Remove .inspect call from test to see if that works...
fdff42b
Remove %i array syntax for older ruby versions
9f6e792
Remove the send_time part of the error message to see if that is caus…
1929b11
Replace new Hash key syntax with old => format for Ruby 1.8.7
3b507fb
Call strftime in test to get some semblance of timestamp verification
f588547
Fix Ruby 1.8.7 syntax errors
0d1b0c9
Fix ActiveJob::Base.queue_adapter error
b08f1c7
Refactor HaveEnqueuedMail matcher to be a subclass of the HaveEnqueue…
5753c6c
Support with blocks
b32449e
Test that non-mailer jobs do not appear in the have_enqueued_mail mat…
c5b5895
Make 'deliver_now' a frozen constant
c8cc534
Only pass a mailer-specific block to super if a block is provided
53ac74a
Fix hash rocket syntax for Ruby 1.8.7
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
require "rspec/mocks" | ||
require "rspec/rails/matchers/active_job" | ||
|
||
module RSpec | ||
module Rails | ||
module Matchers | ||
# Matcher class for `have_enqueued_mail`. Should not be instantiated directly. | ||
# | ||
# rubocop: disable Style/ClassLength | ||
# @private | ||
# @see RSpec::Rails::Matchers#have_enqueued_mail | ||
class HaveEnqueuedMail < ActiveJob::HaveEnqueuedJob | ||
MAILER_JOB_METHOD = 'deliver_now'.freeze | ||
|
||
include RSpec::Mocks::ExampleMethods | ||
|
||
def initialize(mailer_class, method_name) | ||
super(mailer_job) | ||
@mailer_class = mailer_class | ||
@method_name = method_name | ||
@mail_args = [] | ||
@args = mailer_args | ||
end | ||
|
||
def description | ||
"enqueues #{@mailer_class.name}.#{@method_name}" | ||
end | ||
|
||
def with(*args, &block) | ||
@mail_args = args | ||
block.nil? ? super(*mailer_args) : super(*mailer_args, &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}".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_args | ||
if @mail_args.any? | ||
base_mailer_args + @mail_args | ||
else | ||
mailer_method_arity = @mailer_class.instance_method(@method_name).arity | ||
|
||
number_of_args = if mailer_method_arity < 0 | ||
(mailer_method_arity + 1).abs | ||
else | ||
mailer_method_arity | ||
end | ||
|
||
base_mailer_args + Array.new(number_of_args) { anything } | ||
end | ||
end | ||
|
||
def base_mailer_args | ||
[@mailer_class.name, @method_name.to_s, MAILER_JOB_METHOD] | ||
end | ||
|
||
def yield_mail_args(block) | ||
Proc.new { |*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[:job] == mailer_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 mailer_job | ||
ActionMailer::DeliveryJob | ||
end | ||
end | ||
# rubocop: enable Style/ClassLength | ||
|
||
# @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(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, mail_method_name) | ||
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 |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a blocker, but why not just use the constant name directly in places?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had the hardest time keeping the constant straight in my head. I kept wanting to call it
ActionDelivery::MailerJob
orActionMailer::DeliverJob
. So I guess out of frustration, I just made it a method 😅