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?
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
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #blog-post
- #elixir-ls
- #ai
- #elixirconf-us
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 4- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
mariosangiorgio
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:
or something like this:
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 callingtest_helper.exso 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.Collectorstate 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 usemoxwithout 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:
mbuhot
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
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
That a good point!