mariosangiorgio

mariosangiorgio

I am working on a Phoenix application that also uses a GenServer to fetch some data from a third party API on startup and periodically to refresh the values it got.

I’ve got everything working but then I wanted to add some tests, using a mock in place of the external service and I’m having some problems. I’m using mox following what is described in the documentation and in the blog post.

My GenServer looks like this:

defmodule MyApp.Collector do
  use GenServer
  @api Application.get_env(:my_app, :third_party_api)

  def start_link() do
    GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
  end

  defp fetch_new_state() do
    @third_party_api.lookup()
    # put the data into an Agent
  end

  defp schedule_work() do
    refresh_interval = 10 * 60 * 1000
    Process.send_after(self(), :refresh, refresh_interval)
  end

  def init(state) do
    schedule_work()
    fetch_new_state()
    {:ok, state}
  end

  def handle_info(:refresh, state) do
    schedule_work()
    fetch_new_state()
    {:noreply, state}
  end

  def handle_info(request, state) do
    super(request, state)
  end
end

The problem I have is that the call to fetch_new_state in the init callback would call the mock before I have a chance to set any expectation: (Mox.UnexpectedCallError) no expectation defined for MyApp.ThirdPartyApi.Mock.lookup/0 in process #PID<0.293.0>

I tried to add expectations as early as possible in test_helper.ex, but that didn’t help because mix tries to start all the processes in the supervision tree as part of the mix task, which I believe always happen before any call to test_helper.ex.

I could use mix test --no-start to prevent mix from starting the supervision tree, but that feels wrong because then I would need to manually start all the applications in the correct order.

Another possible workaround is to just schedule some work to be done in init, without calling the api but I’m not very convinced by this approach either.

Is it fine to do some work in the init callback of a GenServer? If it is, how do you handle a scenario like the one I described above?

Showing Posts 1 to 4

mariosangiorgio

mariosangiorgio OP

I found some articles (e.g. this one) saying that it’s better to avoid doing expensive operation on init and instead do something like:

defmodule MyServer do
  use GenServer

  def init([]) do
    {:ok, [], 0}
  end

  def handle_info(:timeout, _state) do
    state = something_slow()

    {:noreply, state}
  end
end

or something like this:

defmodule MyServer do
  use GenServer

  def init([]) do
    send self(), :my_fancy_setup

    {:ok, []}
  end

  def handle_info(:my_fancy_setup, _state) do
    state = something_slow()

    {:noreply, state}
  end
end

or even to just call the function that provides the initial state from outside the GenServer and pass it in GenServer.start_link.

Regarding mox, I am convinced that I was trying to use it wrong. First of all, the test bring up all the applications before calling test_helper.ex so setting expectations there is hopeless. Moreover, by default expectations want to be tied to a process and I would need to set the global mode, which I shouldn’t need.

I think that what I really want is to test in isolation my MyApp.Collector state refreshing (and I can do that creating a fresh instance of the GenServer) while disabling it completely when I want to test that my controllers are able to query it properly, in that case I should be able to use mox without problems.

The only thing that this wouldn’t support are async test cases, but from the module documentation autogenerated by the Phoenix template it seems that it’s already off for everything that has to interact with the database:

Finally, if the test case interacts with the database, it cannot be async. For this reason, every test runs inside a transaction which is reset at the beginning of the test unless the test case is marked as async.

mbuhot

mbuhot

unless the test case is marked as async.

You can certainly run async tests against the database using the Ecto 2 sandbox.

It’s just not a safe default for Phoenix to run tests async since you may be interacting with stateful named processes.

idi527

idi527

defmodule MyServer do
  use GenServer

  def init([]) do
    {:ok, [], 0}
  end

  def handle_info(:timeout, _state) do
    state = something_slow()

    {:noreply, state}
  end
end

handle_info(:timeout, _state) would also be called if you return {:reply, reply, state, timeout} or {:noreply, state, timeout} from other handlers and then until you get another message and then again given you still return timeout in the reply/noreply tuples, so it’s a rather dangerous “idiom”. send(self(), :my_fancy_setup) seems safer to me.

mariosangiorgio

mariosangiorgio OP

That a good point!

— All posts loaded —

Where Next? Top

Trending in Questions Top

katta
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
apz
I’m new to elixir and just tried to install the elixirLS extension for VScode(ium) and it is throwing some errors that I would like help ...
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
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews