ream88

ream88

Tracing and asserting function calls

I recently encountered an issue where a partial or functional component was re-rendered multiple times, resulting in an inline function call being executed multiple times as well. This led to multiple database requests instead of just one. The problem was challenging to debug. I initially had some flaky tests and discovered that the issue was not related to render_async which was my first suspect. Instead, something else was happening with my live view. Using an IO.puts call, I found that my function was called six times instead of the expected single call.

I resolved the problem by assigning the specific assigns my function call depended on, rather than using {assigns} when rendering the partial.

My question for the Elixir community is: Is there a way to trace and debug such scenarios? I’m aware of :dbg and the possibility of mocking function calls using mox or similar tools. However, I’m wondering if there’s an existing library that allows setting expectations about function calls and asserting on them.

Marked As Solved

Also Liked

LostKobrakai

LostKobrakai

Take a look at Extrace. It’ll format traces with elixir syntax over erlang syntax.

ream88

ream88

Opps Sunday afternoon programming escalation:

defmodule Tracer do
  @moduledoc false

  import ExUnit.Assertions, only: [assert: 1]

  def start_link do
    Agent.start_link(fn -> %{expectations: %{}, traces: %{}} end, name: __MODULE__)

    :dbg.start()

    :dbg.tracer(
      :process,
      {fn {:trace, _pid, :call, {module, function, args}}, _state ->
        Agent.update(__MODULE__, fn state ->
          update_in(state, [:traces, {module, function, length(args)}], fn
            nil -> 1
            count -> count + 1
          end)
        end)
      end, nil}
    )

    :dbg.p(:all, :c)

    :ok
  end

  def expect(module, function, arity, count \\ 1) do
    Agent.update(__MODULE__, fn state ->
      put_in(state, [:expectations, {module, function, arity}], count)
    end)

    :dbg.tp(module, function, arity, [])

    :ok
  end

  def verify do
    %{expectations: expectations, traces: traces} = Agent.get(__MODULE__, & &1)
    assert expectations == traces
  end

  def verify_on_exit! do
    ExUnit.Callbacks.on_exit(Tracer, fn -> Tracer.verify() end)
  end
end

This module can be used similar to Mox like this:

test "renders list of posts", %{user: user, conn: conn} do
  Tracer.expect(MyApp.Posts, :get_posts, 1, count)
  # ...
end

Now it properly asserts that this function is called count times:

 Assertion with == failed
 code:  assert expectations == traces
 left:  %{{MyApp.Posts, : get_posts, 1} => 2}
 right: %{{MyApp.Posts, : get_posts, 1} => 1}

Will probably clean it up a little bit more (for example replace the Agent with a proper GenServer and implement nicer error messages) and then publish it on hex :blush: Any suggestions for a nice name?

ream88

ream88

I now ended up with the following helper for my ExUnit.Cases:

def setup_trace(%{trace: mfas}) when is_list(mfas) do
  :dbg.start()
  :dbg.tracer()
  :dbg.p(:all, :c)

  Enum.each(mfas, fn {mod, fun, arity} -> :dbg.tp(mod, fun, arity, []) end)

  on_exit(fn ->
    :dbg.stop()
  end)
end

def setup_trace(%{trace: mfa}), do: setup_trace(%{trace: [mfa]})

def setup_trace(_tags), do: :ok

This allows me to tag my tests:

@tag trace: {MyApp.Posts, :get_posts, 1}
test "renders list of posts", %{user: user, conn: conn} do
  # ...
end

This is better than nothing as it allows me to at least visually see multiple calls of my functions. I will maybe investigate a little bit more to check if I can come up with custom assertions.

Where Next?

Popular in Questions Top

jaysoifer
Is there a way to rollback a specific migration and only that one (“skipping” all the other ones)? Would mix ecto.rollback -v 200809061...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" => #BSON.ObjectId<58eb1a7a9ad169198c3dXXXX>, "email" => ...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New

Other popular topics Top

jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
gausby
I asked this very same question on twitter and got some interesting feedback, but I thought it would be a good question to ask here as we...
1207 39467 209
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New
marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
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
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New

Latest on Elixir Forum

We're in Beta

About us Mission Statement