-
Notifications
You must be signed in to change notification settings - Fork 333
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
Better test helpers #109
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
defmodule Bamboo.Test do | ||
@timeout 100 | ||
|
||
import ExUnit.Assertions | ||
|
||
@moduledoc """ | ||
Helpers for testing email delivery | ||
|
||
|
@@ -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 | ||
|
||
|
@@ -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} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be indented too? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
|
||
""" | ||
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 |
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 think this should be configurable. Also should be renamed to
assert_timeout
since we have a separate timeout value for refuteThere 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.
Changed my mind. Hardcoded at 100ms is fine for now (for assertion). I did add required configuration when refuting and using process_name though.