tubedude
HWY Tackle: Deep Req.Test.allow/3 or adding Test-Specific Code in Production Function
Hi everyone,
I’m working on a GenServer module to handle incoming messages in my Elixir application. I’m looking for feedback on how I’m currently handling test-specific code within a production function. Below, I’ve provided the relevant parts of the code for context.
In my test, I create a Req.Test.Stub, but the actual Req.request occurs in a Task that is spawned within the GenServer.
I attempted to bubble up the pid from the Task in the test, but this caused the function execution to occur before the Req.Test.allow could be registered, raising an error that would only happen in the test environment. I also tried using Req.Test.set_req_test_to_shared/1, but I couldn’t get it to work as intended based on the documentation.
As an alternative, I decided to pass the test process pid down to the GenServer and into the Task, where I register Req.Test.allow/3 and add a slight delay to ensure the test runs consistently. This approach is working fine, and I’m happy with it because it avoids introducing extra dependencies. However, I’m unsure if adding test conditionals to the production environment is the right approach.
In other words, how would you tackle this issue?
I’m eager to hear your thoughts.
defmodule MyApp.IncomingTest do
use ExUnit.Case, async: false
alias MyApp.Test.Factory
describe "get_count/0" do
# To test this scenario, I need both messages to work without causing the process to
# exit. But since the Stub is not passed to the process, it would break. The Req plug
# configuration is passed in the `config/test.exs`.
@tag :capture_log
test "two messages" do
{:ok, incoming_pid} = MyApp.Incoming.start_link([])
Req.Test.stub(MyApp.Stub, fn conn ->
Req.Test.json(conn, Factory.create_transcription_response())
end)
ptt =
~w|test support recorded ptt.json|
|> MyApp.TestHelpers.read_and_decode_json()
chat =
~w|test support recorded chat.json|
|> MyApp.TestHelpers.read_and_decode_json()
MyApp.Incoming.message_received(ptt)
assert MyApp.Incoming.get_count() == 1
MyApp.Incoming.message_received(chat)
assert MyApp.Incoming.get_count() == 2
:timer.sleep(2000)
Agent.stop(incoming_pid, :normal)
end
end
end
defmodule MyApp.Incoming do
use GenServer
# API
def message_received(params) do
GenServer.call(__MODULE__, {:message_received, params})
end
def handle_call({:message_received, params}, {from, _ref}, state) do
{:ok, pid} = process_message(params, from)
new_state =
state
|> Map.update(:count, 0, &(&1 + 1))
|> put_in([:processes, pid], params)
{:reply, pid, new_state}
end
# Private functions
defp process_message(params, from) do
{:ok, pid} =
Task.start(fn ->
if System.get_env("MIX_ENV") == "test" do
Req.Test.allow(MyApp.Stub, from, self())
:timer.sleep(50)
end
with {:ok, message} <- cast_message(params),
{:ok, post_processed_message} <- MyApp.Messages.postprocess_message(message) do
post_processed_message
else
{:error, reason} ->
Process.exit(self(), reason)
end
end)
Process.monitor(pid)
{:ok, pid}
end
Looking forward to your feedback.
Popular in Questions
Other popular 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
- #javascript
- #code-sync
- #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
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








