axelson
Bypass and async tests with ex_unit
I have a question for people who have been using bypass for testing web requests. How do you handle asynchronous tests where the code making the request is pretty far into your Phoenix context?
If I’m testing the module that makes the request directly then as an optional argument I can pass the url with the bypass port, but if the request is happening further down the call stack then I don’t want to be passing that url all the way through the application.
Do people just use mox/meck for this instead?
First Post!
trisolaran
whenever I’m in a situation similar to the one you’re describing, I typically make the module that calls the external service configurable so that the external service’s url can be passed as a config parameter. Then, in the config for the test environment (test.exs), I set the url to be the bypass url.
Most Liked
josevalim
You can look at how we tackled this in Bytepack: bytepack_archive/apps/bytepack/test/support/stripe_helpers.ex at main · dashbitco/bytepack_archive · GitHub
We defined a behaviour for the host (in this case Stripe) and then used Mox to set the Bypass URL as the host.
stefanchrobot
If I’m not mistaken, you can’t do Application.put_env (and revert it at the end) in async tests because the tests will override the values. As you mentioned, you can do it globally in config/test.exs, but the way I understood it is that @axelson doesn’t want a global config.
So it seems the options are:
async: falsewithApplication.put_env,async: truewith global config,async: truewith Mox (it uses a global config to replace the real module with the mock).
ananthakumaran
The real implementation was a bit more complicated, but this covers the essential idea (untested). The test will call MyConfig.register(app, key, url) from the before callback (url constructed from the port obtained from bypass). The application code will call MyConfig.get(app, key) and the lookup call will return an overridden value if set, otherwise, will fall back to the Application.get_env. The lookup can handle Task.spawn etc, since most of them set $callers/$ancestors.
defmodule MyConfig do
def get(app, key) do
if test? do
case lookup({:__config_override, key}, [self()]) do
nil -> Application.get_env(app, key)
value -> value
end
else
Application.get_env(app, key)
end
end
def override(_app, key, value) do
Process.put({:__config_override, key}, value)
end
defp lookup(key, list), do: lookup(key, list, MapSet.new())
defp lookup(_key, [], _visited), do: nil
defp lookup(key, [head | rest], visited) do
if !MapSet.member?(visited, head) do
case __get(head, key) do
nil ->
list = Enum.concat([rest, __get(head, :"$callers", []), __get(head, :"$ancestors", [])])
lookup(key, list, MapSet.put(visited, head))
value ->
value
end
else
lookup(key, rest, visited)
end
end
defp __get(process, key), do: __get(process, key, nil)
defp __get(process, key, default) when is_atom(process) do
__get(Process.whereis(process), key, default)
end
defp __get(process, key, default) do
{:dictionary, dictionary} = Process.info(process, :dictionary)
:proplists.get_value(key, dictionary, default)
end
end
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #elixirconf-us
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex









