diff --git a/actionmailer/lib/action_mailer/test_helper.rb b/actionmailer/lib/action_mailer/test_helper.rb index a5ed233ee9e..b94a6441fe9 100644 --- a/actionmailer/lib/action_mailer/test_helper.rb +++ b/actionmailer/lib/action_mailer/test_helper.rb @@ -94,18 +94,43 @@ module ActionMailer end # Asserts that a specific email has been enqueued, optionally - # matching arguments. + # matching arguments and/or params. # # def test_email # ContactMailer.welcome.deliver_later # assert_enqueued_email_with ContactMailer, :welcome # end # + # def test_email_with_parameters + # ContactMailer.with(greeting: "Hello").welcome.deliver_later + # assert_enqueued_email_with ContactMailer, :welcome, args: { greeting: "Hello" } + # end + # # def test_email_with_arguments # ContactMailer.welcome("Hello", "Goodbye").deliver_later # assert_enqueued_email_with ContactMailer, :welcome, args: ["Hello", "Goodbye"] # end # + # def test_email_with_named_arguments + # ContactMailer.welcome(greeting: "Hello", farewell: "Goodbye").deliver_later + # assert_enqueued_email_with ContactMailer, :welcome, args: [{ greeting: "Hello", farewell: "Goodbye" }] + # end + # + # def test_email_with_parameters_and_arguments + # ContactMailer.with(greeting: "Hello").welcome("Cheers", "Goodbye").deliver_later + # assert_enqueued_email_with ContactMailer, :welcome, params: { greeting: "Hello" }, args: ["Cheers", "Goodbye"] + # end + # + # def test_email_with_parameters_and_named_arguments + # ContactMailer.with(greeting: "Hello").welcome(farewell: "Goodbye").deliver_later + # assert_enqueued_email_with ContactMailer, :welcome, params: { greeting: "Hello" }, args: [{farewell: "Goodbye"}] + # end + # + # def test_email_with_parameterized_mailer + # ContactMailer.with(greeting: "Hello").welcome.deliver_later + # assert_enqueued_email_with ContactMailer.with(greeting: "Hello"), :welcome + # end + # # If a block is passed, that block should cause the specified email # to be enqueued. # @@ -123,9 +148,15 @@ module ActionMailer # ContactMailer.with(email: 'user@example.com').welcome.deliver_later # end # end - def assert_enqueued_email_with(mailer, method, args: nil, queue: ActionMailer::Base.deliver_later_queue_name || "default", &block) + def assert_enqueued_email_with(mailer, method, params: nil, args: nil, queue: ActionMailer::Base.deliver_later_queue_name || "default", &block) + if mailer.is_a? ActionMailer::Parameterized::Mailer + params = mailer.instance_variable_get(:@params) + mailer = mailer.instance_variable_get(:@mailer) + end args = if args.is_a?(Hash) [mailer.to_s, method.to_s, "deliver_now", params: args, args: []] + elsif params.present? + [mailer.to_s, method.to_s, "deliver_now", params: params, args: Array(args)] else [mailer.to_s, method.to_s, "deliver_now", args: Array(args)] end diff --git a/actionmailer/test/test_helper_test.rb b/actionmailer/test/test_helper_test.rb index e261629ab4d..2300b446bc1 100644 --- a/actionmailer/test/test_helper_test.rb +++ b/actionmailer/test/test_helper_test.rb @@ -17,6 +17,12 @@ class TestHelperMailer < ActionMailer::Base from: "tester@example.com" end + def test_named_args(recipient:, name:) + mail body: render(inline: "Hello, #{name}"), + to: recipient, + from: "tester@example.com" + end + def test_parameter_args mail body: render(inline: "All is #{params[:all]}"), to: "test@example.com", @@ -393,6 +399,46 @@ class TestHelperMailerTest < ActionMailer::TestCase end end + def test_assert_enqueued_email_with_with_parameterized_mailer + assert_nothing_raised do + assert_enqueued_email_with TestHelperMailer.with(all: "good"), :test_parameter_args do + silence_stream($stdout) do + TestHelperMailer.with(all: "good").test_parameter_args.deliver_later + end + end + end + end + + def test_assert_enqueued_email_with_with_named_args + assert_nothing_raised do + assert_enqueued_email_with TestHelperMailer, :test_named_args, args: [{ email: "some_email", name: "some_name" }] do + silence_stream($stdout) do + TestHelperMailer.test_named_args(email: "some_email", name: "some_name").deliver_later + end + end + end + end + + def test_assert_enqueued_email_with_with_params_and_args + assert_nothing_raised do + assert_enqueued_email_with TestHelperMailer, :test_args, params: { all: "good" }, args: ["some_email", "some_name"] do + silence_stream($stdout) do + TestHelperMailer.with(all: "good").test_args("some_email", "some_name").deliver_later + end + end + end + end + + def test_assert_enqueued_email_with_with_params_and_named_args + assert_nothing_raised do + assert_enqueued_email_with TestHelperMailer, :test_named_args, params: { all: "good" }, args: [{ email: "some_email", name: "some_name" }] do + silence_stream($stdout) do + TestHelperMailer.with(all: "good").test_named_args(email: "some_email", name: "some_name").deliver_later + end + end + end + end + def test_assert_enqueued_email_with_with_no_block_with_parameterized_args assert_nothing_raised do silence_stream($stdout) do diff --git a/guides/source/testing.md b/guides/source/testing.md index 96653459d2f..5190b708dfc 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -1765,14 +1765,9 @@ class UserMailerTest < ActionMailer::TestCase end ``` -In the test we create the email and store the returned object in the `email` -variable. We then ensure that it was sent (the first assert), then, in the -second batch of assertions, we ensure that the email does indeed contain what we -expect. The helper `read_fixture` is used to read in the content from this file. +In the test we create the email and store the returned object in the `email` variable. We then ensure that it was sent (the first assert), then, in the second batch of assertions, we ensure that the email does indeed contain what we expect. The helper `read_fixture` is used to read in the content from this file. -NOTE: `email.body.to_s` is present when there's only one (HTML or text) part present. -If the mailer provides both, you can test your fixture against specific parts -with `email.text_part.body.to_s` or `email.html_part.body.to_s`. +NOTE: `email.body.to_s` is present when there's only one (HTML or text) part present. If the mailer provides both, you can test your fixture against specific parts with `email.text_part.body.to_s` or `email.html_part.body.to_s`. Here's the content of the `invite` fixture: @@ -1784,17 +1779,89 @@ You have been invited. Cheers! ``` -This is the right time to understand a little more about writing tests for your -mailers. The line `ActionMailer::Base.delivery_method = :test` in -`config/environments/test.rb` sets the delivery method to test mode so that -email will not actually be delivered (useful to avoid spamming your users while -testing) but instead it will be appended to an array -(`ActionMailer::Base.deliveries`). +This is the right time to understand a little more about writing tests for your mailers. The line `ActionMailer::Base.delivery_method = :test` in `config/environments/test.rb` sets the delivery method to test mode so that email will not actually be delivered (useful to avoid spamming your users while testing) but instead it will be appended to an array (`ActionMailer::Base.deliveries`). -NOTE: The `ActionMailer::Base.deliveries` array is only reset automatically in -`ActionMailer::TestCase` and `ActionDispatch::IntegrationTest` tests. -If you want to have a clean slate outside these test cases, you can reset it -manually with: `ActionMailer::Base.deliveries.clear` +NOTE: The `ActionMailer::Base.deliveries` array is only reset automatically in `ActionMailer::TestCase` and `ActionDispatch::IntegrationTest` tests. If you want to have a clean slate outside these test cases, you can reset it manually with: `ActionMailer::Base.deliveries.clear` + +#### Testing Enqueued Emails + +You can use the `assert_enqueued_email_with` assertion to confirm that the email has been enqueued with all of the expected mailer method arguments and/or parameterized mailer parameters. This allows you to match any email that have been enqueued with the `deliver_later` method. + +As with the basic test case, we create the email and store the returned object in the `email` variable. The following examples include variations of passing arguments and/or parameters. + +This example will assert that the email has been enqueued with the correct arguments: + +```ruby +require "test_helper" + +class UserMailerTest < ActionMailer::TestCase + test "invite" do + # Create the email and store it for further assertions + email = UserMailer.create_invite("me@example.com", "friend@example.com") + + # Test that the email got enqueued with the correct arguments + assert_enqueued_email_with UserMailer, :create_invite, args: ["me@example.com", "friend@example.com"] do + email.deliver_later + end + end +end +``` + +This example will assert that a mailer has been enqueued with the correct mailer method named arguments by passing a hash of the arguments as `args`: + +```ruby +require "test_helper" + +class UserMailerTest < ActionMailer::TestCase + test "invite" do + # Create the email and store it for further assertions + email = UserMailer.create_invite(from: "me@example.com", to: "friend@example.com") + + # Test that the email got enqueued with the correct named arguments + assert_enqueued_email_with UserMailer, :create_invite, args: [{ from: "me@example.com", + to: "friend@example.com" }] do + email.deliver_later + end + end +end +``` + +This example will assert that a parameterized mailer has been enqueued with the correct parameters and arguments. The mailer parameters are passed as `params` and the mailer method arguments as `args`: + +```ruby +require "test_helper" + +class UserMailerTest < ActionMailer::TestCase + test "invite" do + # Create the email and store it for further assertions + email = UserMailer.with(all: "good").create_invite("me@example.com", "friend@example.com") + + # Test that the email got enqueued with the correct mailer parameters and arguments + assert_enqueued_email_with UserMailer, :create_invite, params: { all: "good" }, + args: ["me@example.com", "friend@example.com"] do + email.deliver_later + end + end +end +``` + +This example shows an alternative way to test that a parameterized mailer has been enqueued with the correct parameters: + +```ruby +require "test_helper" + +class UserMailerTest < ActionMailer::TestCase + test "invite" do + # Create the email and store it for further assertions + email = UserMailer.with(to: "friend@example.com").create_invite + + # Test that the email got enqueued with the correct mailer parameters + assert_enqueued_email_with UserMailer.with(to: "friend@example.com"), :create_invite do + email.deliver_later + end + end +end +``` ### Functional and System Testing @@ -1831,7 +1898,7 @@ class UsersTest < ActionDispatch::SystemTestCase end ``` -NOTE: The `assert_emails` method is not tied to a particular deliver method and will work with emails delivered with either the `deliver_now` or `deliver_later` method. If we explicitly want to assert that the email has been enqueued we can use the `assert_enqueued_emails` method. More information can be found in the [documentation here](https://api.rubyonrails.org/classes/ActionMailer/TestHelper.html). +NOTE: The `assert_emails` method is not tied to a particular deliver method and will work with emails delivered with either the `deliver_now` or `deliver_later` method. If we explicitly want to assert that the email has been enqueued we can use the `assert_enqueued_email_with` ([examples above](#testing_enqueued_emails)) or `assert_enqueued_emails` methods. More information can be found in the [documentation here](https://api.rubyonrails.org/classes/ActionMailer/TestHelper.html). Testing Jobs ------------