Skip to content
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

Improvements to assert_enqueued_email_with #45752

Merged
merged 13 commits into from Aug 9, 2022
35 changes: 33 additions & 2 deletions actionmailer/lib/action_mailer/test_helper.rb
Expand Up @@ -94,18 +94,43 @@ def assert_enqueued_emails(number, &block)
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.
#
Expand All @@ -123,9 +148,15 @@ def assert_enqueued_emails(number, &block)
# 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
Expand Down
46 changes: 46 additions & 0 deletions actionmailer/test/test_helper_test.rb
Expand Up @@ -17,6 +17,12 @@ def test_args(recipient, name)
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",
Expand Down Expand Up @@ -393,6 +399,46 @@ def test_assert_enqueued_email_with_with_parameterized_args
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
Expand Down
103 changes: 85 additions & 18 deletions guides/source/testing.md
Expand Up @@ -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:

Expand All @@ -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

Expand Down Expand Up @@ -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
------------
Expand Down