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

Better test helpers #109

Merged
merged 1 commit into from Apr 13, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
164 changes: 135 additions & 29 deletions lib/bamboo/test.ex
@@ -1,6 +1,8 @@
defmodule Bamboo.Test do
@timeout 100
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be configurable. Also should be renamed to assert_timeout since we have a separate timeout value for refute

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed my mind. Hardcoded at 100ms is fine for now (for assertion). I did add required configuration when refuting and using process_name though.


import ExUnit.Assertions

@moduledoc """
Helpers for testing email delivery

Expand Down Expand Up @@ -147,10 +149,10 @@ defmodule Bamboo.Test do
@doc """
Checks whether an email was delivered.

Must be used with the Bamboo.TestAdapter or this will never pass. If a
Bamboo.Email struct is passed in, it will check that all fields are matching.

You can also pass a keyword list and it will check just the fields you pass in.
Must be used with the `Bamboo.TestAdapter` or this will never pass. In case you
are delivering from another process, the assertion waits up to 100ms before
failing. Typically if an email is successfully delivered the assertion will
pass instantly, so test suites will remain fast.

## Examples

Expand All @@ -162,49 +164,153 @@ defmodule Bamboo.Test do
assert_delivered_email(unsent_email) # Will fail
"""
def assert_delivered_email(%Bamboo.Email{} = email) do
import ExUnit.Assertions
email = Bamboo.Mailer.normalize_addresses(email)
assert_receive {:delivered_email, ^email}, @timeout
do_assert_delivered_email(email)
end

defp do_assert_delivered_email(email) do
receive do
{:delivered_email, ^email} -> true
after
@timeout -> flunk_with_email_list(email)
end
end

defp flunk_with_email_list(email) do
if Enum.empty?(delivered_emails) do
flunk """
There were 0 emails delivered to this process.

If you expected an email to be sent, try these ideas:

1) Make sure you call deliver_now/1 or deliver_later/1 to deliver the email
2) Make sure you are using the Bamboo.TestAdapter
3) Use the process_name feature of Bamboo.Test. This will allow Bamboo.Test
to work across processes: use Bamboo.Test, process_name: :my_test_name
4) If you are writing an acceptance test through a headless browser, use
the process_name feature described in option 3.
"""
else
flunk """
There were no matching emails.

No emails matched:

#{inspect email}

Delivered emails:

#{delivered_emails_as_list}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be indented too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem was that if I indented in this string it would indent the first email, but the rest wouldn’t be indented. So I had to do the indenting in the delivered_emails_as_list function. Open to other ideas if you can think of something.

On Apr 8, 2016, at 11:13 AM, Josh Steiner notifications@github.com wrote:

In lib/bamboo/test.ex #109 (comment):

  •       to work across processes: use Bamboo.Test, process_name: :my_test_name
    
  •    4) If you are writing an acceptance test through a headless browser, see
    
  •       if writing a controller test would be possible instead. Otherwise try
    
  •       option 3.
    
  •  """
    
  • else
  •  flunk """
    
  •  There were no matching emails.
    
  •  No emails matched:
    
  •    #{inspect email}
    
  •  Delivered emails:
    
  •  #{delivered_emails_as_list}
    
    Should this be indented too?


You are receiving this because you authored the thread.
Reply to this email directly or view it on GitHub https://github.com/thoughtbot/bamboo/pull/109/files/6b0e3abf62fa061c8fd7187bf78494cd729edb8a#r59040183

"""
end
end
def assert_delivered_email(email_options) when is_list(email_options) do
import ExUnit.Assertions
email = Bamboo.Email.new_email(email_options)
|> Bamboo.Mailer.normalize_addresses
assert_receive {:delivered_email, ^email}, @timeout

defp delivered_emails do
{:messages, messages} = Process.info(self, :messages)

for {:delivered_email, _} = email_message <- messages do
email_message
end
end

defp delivered_emails_as_list do
delivered_emails |> add_asterisk |> Enum.join("\n")
end

defp add_asterisk(emails) do
Enum.map(emails, &" * #{inspect &1}")
end

@doc """
Checks that no emails were sent.

Under the hood this uses `ExUnit.Assertions.refute_received`, so if you are
delivering emails from another process (for example, within Task.async) you
may have a race condition where this assertion is called before the email was
received by the test process, resulting in an incorrect test.
If used with the process_name feature of `Bamboo.Test`, you must also configure
a timeout in your test config.

# Set this in your config, typically in config/test.exs
config :bamboo, :refute_timeout, 10

The value you set is up to you. Lower values will result in faster tests,
but may incorrectly pass if an email is delivered *after* the timeout. Often
times 1ms is enough.
"""
def assert_no_emails_delivered do
receive do
{:delivered_email, email} -> flunk_with_unexpected_email(email)
after
refute_timeout -> true
end
end

@doc false
def assert_no_emails_sent do
import ExUnit.Assertions
refute_received {:delivered_email, _}
raise "assert_no_emails_sent/0 has been renamed to assert_no_emails_delivered/0"
end

defp flunk_with_unexpected_email(email) do
flunk """
Unexpectedly delivered an email when expected none to be delivered.

Delivered email:

#{inspect email}
"""
end

@doc """
Ensures a particular email was not sent

Same as assert_delivered_email, except it checks that an email was not sent.
Same as `assert_delivered_email/0`, except it checks that a particular email
was not sent.

If used with the process_name feature of `Bamboo.Test`, you must also configure
a timeout in your test config.

Under the hood this uses `ExUnit.Assertions.refute_received`, so if you are
delivering emails from another process (for example, within Task.async) you
may have a race condition where this assertion is called before the email was
received by the test process, resulting in an incorrect test.
# Set this in your config, typically in config/test.exs
config :bamboo, :refute_timeout, 10

The value you set is up to you. Lower values will result in faster tests,
but may incorrectly pass if an email is delivered *after* the timeout. Often
times 1ms is enough.
"""
def refute_delivered_email(%Bamboo.Email{} = email) do
import ExUnit.Assertions
email = Bamboo.Mailer.normalize_addresses(email)
refute_received {:delivered_email, ^email}

receive do
{:delivered_email, ^email} -> flunk_with_unexpected_matching_email(email)
after
refute_timeout -> true
end
end

defp flunk_with_unexpected_matching_email(email) do
flunk """
Unexpectedly delivered a matching email.

Matched email that was delivered:

#{inspect email}
"""
end
def refute_delivered_email(email_options) when is_list(email_options) do
import ExUnit.Assertions
email = Bamboo.Email.new_email(email_options)
|> Bamboo.Mailer.normalize_addresses
refute_received {:delivered_email, ^email}

defp refute_timeout do
if using_process_name? do
Application.get_env(:bamboo, :refute_timeout) || raise """
When using process_name with Bamboo.Test, you must set a timeout. This
is because an email can be delivered after the assertion is called.

# Set this in your config, typically in config/test.exs
config :bamboo, :refute_timeout, 10

The value you set is up to you. Lower values will result in faster tests,
but may incorrectly pass if an email is delivered *after* the timeout.
"""
else
0
end
end

defp using_process_name? do
!!Application.get_env(:bamboo, :test_process_name)
end
end
103 changes: 95 additions & 8 deletions test/lib/bamboo/adapters/test_adapter_test.exs
Expand Up @@ -28,7 +28,7 @@ defmodule Bamboo.TestAdapterTest do
end
end

test "deliver/2 sends a message to the process" do
test "deliver sends a message to the process" do
email = new_email()

email |> TestAdapter.deliver(@config)
Expand All @@ -44,20 +44,107 @@ defmodule Bamboo.TestAdapterTest do

assert_delivered_email sent_email
refute_delivered_email unsent_email

sent_email |> TestMailer.deliver_now
assert_raise ExUnit.AssertionError, fn ->
assert_delivered_email %{sent_email | to: "oops"}
end
end

test "assert_delivered_email with no delivered emails" do
sent_email = new_email(from: "foo@bar.com", to: ["foo@bar.com"])

try do
assert_delivered_email %{sent_email | to: "oops"}
rescue
error in [ExUnit.AssertionError] ->
assert error.message =~ "0 emails delivered"
else
_ -> flunk "assert_delivered_email should failed"
end
end

test "assert_no_emails_delivered raises helpful error message" do
assert_raise RuntimeError, ~r/has been renamed/, fn ->
assert_no_emails_sent
end
end

test "helpers for testing against parts of an email" do
recipient = {nil, "foo@bar.com"}
sent_email = new_email(from: "foo@bar.com", to: [recipient])
test "assert_delivered_email shows non-matching delivered emails" do
sent_email = new_email(from: "foo@bar.com", to: ["foo@bar.com"])

sent_email |> TestMailer.deliver_now

refute_delivered_email(from: "someoneelse@bar.com")
assert_delivered_email(from: "foo@bar.com", to: "foo@bar.com")
try do
assert_delivered_email %{sent_email | to: "oops"}
rescue
error in [ExUnit.AssertionError] ->
assert error.message =~ "no matching emails"
assert error.message =~ sent_email.from
else
_ -> flunk "assert_delivered_email should failed"
end
end

test "assert_delivered_email filters message that are not emails" do
sent_email = new_email(from: "foo@bar.com", to: ["foo@bar.com"])

TestMailer.deliver_now(sent_email)

send self, :not_an_email

try do
assert_delivered_email %{sent_email | to: "oops"}
rescue
error in [ExUnit.AssertionError] ->
assert error.message =~ "no matching emails"
refute error.message =~ ":not_an_email"
else
_ -> flunk "assert_delivered_email should failed"
end
end

test "assert_no_emails_delivered shows the delivered email" do
sent_email = new_email(from: "foo@bar.com", to: ["foo@bar.com"])

TestMailer.deliver_now(sent_email)

try do
assert_no_emails_delivered
rescue
error in [ExUnit.AssertionError] ->
assert error.message =~ "Unexpectedly delivered an email"
assert error.message =~ sent_email.from
else
_ -> flunk "assert_no_emails_delivered should failed"
end
end

test "refute_delivered_email shows the delivered email" do
sent_email = new_email(from: "foo@bar.com", to: ["foo@bar.com"])

TestMailer.deliver_now(sent_email)

try do
refute_delivered_email sent_email
rescue
error in [ExUnit.AssertionError] ->
assert error.message =~ "Unexpectedly delivered a matching email"
assert error.message =~ sent_email.from
else
_ -> flunk "refute_delivered_email should failed"
end
end

test "assert_no_emails_sent" do
assert_no_emails_sent
test "assert_no_emails_delivered" do
assert_no_emails_delivered

sent_email = new_email(from: "foo@bar.com", to: "whoever")
sent_email |> TestMailer.deliver_now

assert_raise ExUnit.AssertionError, fn ->
assert_no_emails_delivered
end
end

test "assertion helpers format email addresses" do
Expand Down
12 changes: 12 additions & 0 deletions test/lib/bamboo/multi_process_test.exs
Expand Up @@ -22,4 +22,16 @@ defmodule Bamboo.MultiProcessTest do

assert_delivered_email email
end

test "refute_delivered_email with process_name set and with refute_timeout blank, raises an error" do
assert_raise RuntimeError, ~r/set a timeout/, fn ->
refute_delivered_email new_email(from: "someone")
end
end

test "assert_no_emails_delivered with process_name set and with refute_timeout blank, raises an error" do
assert_raise RuntimeError, ~r/set a timeout/, fn ->
assert_no_emails_delivered
end
end
end