axelson

axelson

Scenic Core Team

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

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

josevalim

Creator of Elixir

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

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: false with Application.put_env,
  • async: true with global config,
  • async: true with Mox (it uses a global config to replace the real module with the mock).
ananthakumaran

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

Where Next?

Trending in Questions Top

lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" => #BSON.ObjectId<58eb1a7a9ad169198c3dXXXX>, "email" => ...
New
tj0
I’ve been following the steps here for the upgrade from 1.6 to 1.7 and it has gone relatively smoothly all the way till the phoenix_view ...
New
cgraham
Hi! What is currently the best library/method for parsing text and tabular data out of PDF files in Elixir or Erlang?
New
stefanchrobot
Hi, I need a way to handle data migrations in my application. I found an article by @wojtekmach about manual migrations: Automatic and ma...
New
stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
kip
Localize is the next generation localisation library for Elixir. Think of it as ex_cldr version 3.0. The first version will be released ...
New
webofbits
Squid Mesh is an open source workflow automation runtime for Elixir applications. It is aimed at Phoenix and OTP apps that want to defin...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
kip
In 2021 I started a new library called Tempo with the objective of modelling time as a set of intervals - not as instants. In 2022 I gave...
New

We're in Beta

About us Mission Statement