Metaprogramming a Function With Dynamic Params

I have this code in a test, which doesn’t quite work:

defmodule TurtleTest do
  use ExUnit.Case, async: true

  defmodule WorldSpy do
    Turtles.World.__info__(:functions)
    |> Enum.map(fn {name, arity} ->
      params =
        Stream.unfold(0, fn n -> {:"param_#{n}", n + 1} end)
        |> Enum.take(arity)    # I need to get this…
      def unquote(name)( ) do  # to be the list of params here
        IO.puts unquote(to_string name) <> " called"
      end
    end)
  end

  # ...
end

See the comments above, but is there a way for me to dynamically build up the parameter list like this?

Can you talk about what you’re trying to do more first? I once had a similar approach in ExAws and it was I believe a mistaken approach.

Sure.

I would like to build a module that is a test double spy for another module. It would support the same function calls with the same arities. All those functions would do is log calls to them. Afterward, I could make assertions about those calls.

I welcome any critique of my approach.

I have read José’s mocking article and I believe it supports this usage, which seems more mock as a noun than verb to me.

Regardless of the merit of my intentions though, it would be interesting to also learn the answer to my asked question, if possible.

You can do something like:

defmodule Turtles.World do
  def foo(a, b), do: a + b
  def bar(a), do: a
  def baz(a, b, c), do: a + b + c
end

defmodule WorldSpy do
  Turtles.World.__info__(:functions)
  |> Enum.map(fn {name, arity} ->
    params =
      for _ <- 1..arity, do: {:_, [], nil}
    def unquote(name)(unquote_splicing(params)) do  # to be the list of params here
      IO.puts unquote(to_string name) <> " called"
    end
  end)
end

WorldSpy.__info__(:functions) |> IO.inspect # [bar: 1, baz: 3, foo: 2]
WorldSpy.foo(1, 2)
WorldSpy.bar(1)
WorldSpy.baz(1, 2, 3)

Here http://elixir-lang.org/docs/stable/elixir/Kernel.SpecialForms.html#unquote_splicing/1 is used so that the arity of the function matches.

I have replaced your stream with a comprehension that returned a quoted _ so that the arguments are ignored.

1 Like

Super nifty. Thanks.