-
Notifications
You must be signed in to change notification settings - Fork 226
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
Add support for integration testing #66
Comments
👍 Prior to this getting implemented, is there another way to test asynchronous deliveries, like from within a |
Depending on how we decide to implement this, we might that tackle both use case in one go. I'll keep your comment in mind when we work on it. |
A possible workaround is to use the I set up a separate mix environment called 'acceptance' that uses Code snippets: https://gist.github.com/monicao/0e31545ea3d37cab342504bd8eeb0a87 |
Thanks for sharing this @monicao |
I'm using a custom adapter for this, following a pattern that I just published a blog post about: defmodule MyApp.SwooshAdapter.Test do
use Swoosh.Adapter
use GenServer
def start_link do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
def subscribe do
GenServer.call(__MODULE__, {:subscribe, self()})
end
def deliver(email, _config) do
GenServer.call(__MODULE__, {:deliver, email})
end
# SERVER
def handle_call({:subscribe, pid}, _from, listeners) do
{:reply, :ok, [pid | listeners]}
end
def handle_call({:deliver, email}, _from, listeners) do
send_to_listeners(listeners, {:email, email})
{:reply, :ok, listeners}
end
defp send_to_listeners(listeners, message) do
for listener <- listeners do
send listener, message
end
end
end |
thanks a lot @manukall !! |
This should work very much like the phoenix_ecto plug for the ecto sandbox: https://gist.github.com/LostKobrakai/9077143caacf534f9c4743cfc18a148e |
@LostKobrakai apologies for not replying earlier. Would you like to submit a PR with that test adapter? |
@stevedomin I've not tested it beyond my own needs and I'm currently quite busy. So if someone wants to make an official adapter work of of it feel free. I haven't got the time to make it more official/proper at the moment. |
@LostKobrakai of course, makes sense! We will take care of it. |
This one is inspired on Bamboo.TestAdapter defmodule Webapp.Mailer.TestAdapter do
use Swoosh.Adapter
def deliver(email, _config) do
send(test_process(), {:email, email})
{:ok, %{}}
end
defp test_process do
Application.get_env(:swoosh, :shared_test_process) || self()
end
end
# in your config/test.exs
config :webapp, Webapp.Mailer, adapter: Webapp.Mailer.TestAdapter So if your Webapp is sending email inside setup do
Application.put_env(:swoosh, :shared_test_process, self())
:ok
end
# and somewhere in your assertions, something like
assert_email_sent(subject: subject, to: recipients)
# or
receive do
{:email, email} ->
# assert other email fiels
# _i.e. Mailgun: at the time of this writting there are no `assert_equal` support to `assert_email_sent`
# with `:provider_options` to assert email contains correct `recipient-variables`
assert email.subject == subject
assert email.to == recipients
assert email.provider_options == %{recipient_vars: recipient_vars}
after
1_000 ->
raise "No updates email delivered"
end |
Would you be open to a pull request to the test adapter that adds this functionality? |
@lpil yes, definitely! |
I tried to add this to the Swoosh Test Adapter, but had a hard time writing tests for it. The async process seems to get tangled up with the test runner. I ended up copying @hisapy's example test adapter into my own project and it is working well. |
We have also stumbled upon some problem with testing swoosh in our higher-level tests, as we test there interaction between few processes. We have tried out an approach with "custom" adapter using Mox (https://github.com/plataformatec/mox). With minor workarounds it works quite all right, but I can't say for sure until enough time passes and no problems are discovered. I'm leaving this message here as a tip for others, as I don't have time right now to provide more details, hope I can find time for that a bit later. |
Here is a summary, without project-specific stuff. In test helper (somewhere in defmodule BlahBlah.TestHelpers.BlahBlahEmails do
alias BlahBlah.Mailer.AdapterMock, as: MailerMock
# for multi-process high-level tests
def allow_processes_to_send_mails(%{pids: pids})
when is_list(pids) do
pids |> Enum.each(fn pid ->
:ok = allow_process_to_send_mails(%{pid: pid})
end)
:ok
end
# for single-process high-level tests
def allow_process_to_send_mails(%{pid: pid}) do
test_pid = self()
MailerMock
|> Mox.allow(test_pid, pid)
|> Mox.stub(:validate_config, fn _ -> :ok end)
|> Mox.stub(:deliver, fn email, _config ->
# NOTE: self() here will not be the process of test.
# This is why we need the test_pid that's set up outside.
send(test_pid, {:email, email})
{:ok, %{}}
end)
:ok
end
end Examples of/for error tests are omitted, as principle is pretty much the same, you control what fake adapter returns as result of :deliver. If you already provide all required pids as either In defmodule BlahBlah.Mailer.ValidateConfigAdapterBehaviour do
@callback validate_config(any()) :: :ok
end
Mox.defmock(BlahBlah.Mailer.AdapterMock,
for: [Swoosh.Adapter, BlahBlah.Mailer.ValidateConfigAdapterBehaviour])
Application.put_env(:admin, BlahBlah.Mailer,
adapter: BlahBlah.Mailer.AdapterMock) Unfortunately, In test itself: defmodule BlahBlah.SomeMultiProcessTest do
...
import Mox
import BlahBlah.TestHelpers.BlahBlahEmails
import Swoosh.TestAssertions
...
setup [
... set up processes under tests and such ...
:verify_on_exit!
]
...
test "something", %{pid_a: pid_a, pid_b: pid_b, ...} do
...
allow_process_to_send_mails(%{pids: [pid_a, pid_b, ...]})
# or
allow_process_to_send_mails(%{pid: pid_a})
allow_process_to_send_mails(%{pid: pid_b})
...
do actual test here
...
assert_email_sent BlahBlah.Emails.whatever()
assert_email_sent BlahBlah.Emails.another()
...
end
...
end This way we let Mox handle details of isolating one test from another. As for limitations, while we don't go over 3-4 processes in our tests, I wrote a quick dirty test with bunch of genservers sending fake emails and it looks like it does not break with 20+ processes as well. Any feedback is welcome, especially if you will find any faults that we have not yet noticed. |
A small note, just in case anyone will have same issue - after update today we got weird error in our tests:
Placing the ...
Elixir.BlahBlah.Mailer.AdapterMock: [
{:def, [context: Mox, import: Kernel],
[
{:__mock_for__, [context: Mox], Mox},
[do: [Swoosh.Adapter, BlahBlah.Mailer.ValidateConfigAdapterBehaviour]]
]},
{{:., [], [Swoosh.Adapter, :module_info]}, [], [:module]},
{{:., [], [BlahBlah.Mailer.ValidateConfigAdapterBehaviour, :module_info]}, [],
[:module]},
{:def, [context: Mox, import: Kernel],
[
{:validate_dependency, [context: Mox], []},
[
do: {{:., [], [{:__aliases__, [alias: false], [:Mox]}, :__dispatch__]},
[], [{:__MODULE__, [], Mox}, :validate_dependency, 0, []]}
]
]},
{:def, [context: Mox, import: Kernel],
[
{:validate_config, [context: Mox], [{:arg1, [], Elixir}]},
[
do: {{:., [], [{:__aliases__, [alias: false], [:Mox]}, :__dispatch__]},
[],
[{:__MODULE__, [], Mox}, :validate_config, 1, [{:arg1, [], Elixir}]]}
]
]},
{:def, [context: Mox, import: Kernel],
[
{:deliver, [context: Mox], [{:arg1, [], Elixir}, {:arg2, [], Elixir}]},
[
do: {{:., [], [{:__aliases__, [alias: false], [:Mox]}, :__dispatch__]},
[],
[
{:__MODULE__, [], Mox},
:deliver,
2,
[{:arg1, [], Elixir}, {:arg2, [], Elixir}]
]}
]
]},
{:def, [context: Mox, import: Kernel],
[
{:validate_config, [context: Mox], [{:arg1, [], Elixir}]},
[
do: {{:., [], [{:__aliases__, [alias: false], [:Mox]}, :__dispatch__]},
[],
[{:__MODULE__, [], Mox}, :validate_config, 1, [{:arg1, [], Elixir}]]}
]
]}
]
... This is because So changing defmodule BlahBlah.Mailer.ValidateConfigAdapterBehaviour do
@callback validate_config(any()) :: :ok
end
Mox.defmock(BlahBlah.Mailer.AdapterMock,
for: [Swoosh.Adapter, BlahBlah.Mailer.ValidateConfigAdapterBehaviour]) to just Mox.defmock(BlahBlah.Mailer.AdapterMock,
for: [Swoosh.Adapter]) solves the issue. |
What about an adapter where we can assert on the contents of a queue, not so unlike Oban.drain_queue/1. Seems like the message passing gets pretty sticky, but if there was a general "mailbox" that all emails went to, then finding your specific email would be |
@jc00ke I think you can achieve this with Mox-based setup by spinning a process to store the list of mails and sending message to it in In this case passing messages around is still there, of course. In my (very limited) experience it's not a big problem, the spinning up a process per test may be a bigger one. |
The current TestAdapter breaks with integration testing using hound or wallaby.
The best option seem to be implementing some kind of ownership-based mechanism similar to what Ecto 2 does (elixir-ecto/ecto#1237)
@barisbalic @hubertlepicki @lau I would love to get your thoughts on that
The text was updated successfully, but these errors were encountered: