diff --git a/features/matchers/have_enqueued_mail_matcher.feature b/features/matchers/have_enqueued_mail_matcher.feature index ab1ae50210..8def19cf3c 100644 --- a/features/matchers/have_enqueued_mail_matcher.feature +++ b/features/matchers/have_enqueued_mail_matcher.feature @@ -38,3 +38,91 @@ Feature: have_enqueued_mail matcher """ When I run `rspec spec/mailers/user_mailer_spec.rb` Then the examples should all pass + + Scenario: Checking mailer arguments + Given a file named "app/mailers/my_mailer.rb" with: + """ruby + class MyMailer < ApplicationMailer + + def signup(user = nil) + @user = user + + mail to: "to@example.org" + end + end + """ + Given a file named "spec/mailers/my_mailer_spec.rb" with: + """ruby + require "rails_helper" + + RSpec.describe MyMailer do + it "matches with enqueued mailer" do + ActiveJob::Base.queue_adapter = :test + # Works with plain args + expect { + MyMailer.signup('user').deliver_later + }.to have_enqueued_mail(MyMailer, :signup).with('user') + end + end + """ + When I run `rspec spec/mailers/my_mailer_spec.rb` + Then the examples should all pass + + Scenario: Parameterize the mailer + Given a file named "app/mailers/my_mailer.rb" with: + """ruby + class MyMailer < ApplicationMailer + + def signup + @foo = params[:foo] + + mail to: "to@example.org" + end + end + """ + Given a file named "spec/mailers/my_mailer_spec.rb" with: + """ruby + require "rails_helper" + + RSpec.describe MyMailer do + it "matches with enqueued mailer" do + ActiveJob::Base.queue_adapter = :test + # Works with named parameters + expect { + MyMailer.with(foo: 'bar').signup.deliver_later + }.to have_enqueued_mail(MyMailer, :signup).with(foo: 'bar') + end + end + """ + When I run `rspec spec/mailers/my_mailer_spec.rb` + Then the examples should all pass + + Scenario: Parameterize and pass an argument to the mailer + Given a file named "app/mailers/my_mailer.rb" with: + """ruby + class MyMailer < ApplicationMailer + + def signup(user) + @user = user + @foo = params[:foo] + + mail to: "to@example.org" + end + end + """ + Given a file named "spec/mailers/my_mailer_spec.rb" with: + """ruby + require "rails_helper" + + RSpec.describe MyMailer do + it "matches with enqueued mailer" do + ActiveJob::Base.queue_adapter = :test + # Works also with both, named parameters match first argument + expect { + MyMailer.with(foo: 'bar').signup('user').deliver_later + }.to have_enqueued_mail(MyMailer, :signup).with({foo: 'bar'}, 'user') + end + end + """ + When I run `rspec spec/mailers/my_mailer_spec.rb` + Then the examples should all pass diff --git a/lib/rspec/rails/matchers/have_enqueued_mail.rb b/lib/rspec/rails/matchers/have_enqueued_mail.rb index b7756346e9..8f12876bc9 100644 --- a/lib/rspec/rails/matchers/have_enqueued_mail.rb +++ b/lib/rspec/rails/matchers/have_enqueued_mail.rb @@ -7,6 +7,7 @@ module RSpec module Rails module Matchers + # rubocop: disable Metrics/ClassLength # Matcher class for `have_enqueued_mail`. Should not be instantiated directly. # # @private @@ -76,7 +77,7 @@ def job_match?(job) def arguments_match?(job) @args = if @mail_args.any? - base_mailer_args + @mail_args + base_mailer_args + process_arguments(job, @mail_args) elsif @mailer_class && @method_name base_mailer_args + [any_args] elsif @mailer_class @@ -88,6 +89,18 @@ def arguments_match?(job) super(job) end + def process_arguments(job, given_mail_args) + # Old matcher behavior working with all builtin classes but ActionMailer::MailDeliveryJob + return given_mail_args unless defined?(ActionMailer::MailDeliveryJob) && job[:job] <= ActionMailer::MailDeliveryJob + + # If matching args starts with a hash and job instance has params match with them + if given_mail_args.first.is_a?(Hash) && job[:args][3]['params'].present? + [hash_including(params: given_mail_args[0], args: given_mail_args.drop(1))] + else + [hash_including(args: given_mail_args)] + end + end + def base_mailer_args [mailer_class_name, @method_name.to_s, MAILER_JOB_METHOD] end @@ -120,7 +133,6 @@ def unmatching_mail_jobs_message 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? @@ -142,6 +154,8 @@ def unified_mail?(job) RSpec::Rails::FeatureCheck.has_action_mailer_unified_delivery? && job[:job] <= ActionMailer::MailDeliveryJob end end + # rubocop: enable Metrics/ClassLength + # @api public # Passes if an email has been enqueued inside block. # May chain with to specify expected arguments. diff --git a/spec/rspec/rails/matchers/have_enqueued_mail_spec.rb b/spec/rspec/rails/matchers/have_enqueued_mail_spec.rb index 152e02fffc..119779352e 100644 --- a/spec/rspec/rails/matchers/have_enqueued_mail_spec.rb +++ b/spec/rspec/rails/matchers/have_enqueued_mail_spec.rb @@ -393,18 +393,22 @@ def self.name; "NonMailerJob"; end }.to have_enqueued_mail(UnifiedMailer, :test_email).and have_enqueued_mail(UnifiedMailer, :email_with_args) end - it "passes with provided argument matchers" do + it "matches arguments when mailer has only args" do + expect { + UnifiedMailer.email_with_args(1, 2).deliver_later + }.to have_enqueued_mail(UnifiedMailer, :email_with_args).with(1, 2) + end + + it "matches arguments when mailer is parameterized" do expect { UnifiedMailer.with('foo' => 'bar').test_email.deliver_later - }.to have_enqueued_mail(UnifiedMailer, :test_email).with( - a_hash_including(params: {'foo' => 'bar'}) - ) + }.to have_enqueued_mail(UnifiedMailer, :test_email).with('foo' => 'bar') + end + it "matches arguments when mixing parameterized and non-parameterized emails" do expect { UnifiedMailer.with('foo' => 'bar').email_with_args(1, 2).deliver_later - }.to have_enqueued_mail(UnifiedMailer, :email_with_args).with( - a_hash_including(params: {'foo' => 'bar'}, args: [1, 2]) - ) + }.to have_enqueued_mail(UnifiedMailer, :email_with_args).with({'foo' => 'bar'}, 1, 2) end it "passes when using a mailer with `delivery_job` set to a sub class of `ActionMailer::DeliveryJob`" do