jjh42 / mock

Mocking library for Elixir language

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Not working, at least not in the way I expected

naps62 opened this issue · comments

I'm finding that this package does not work when mocking functions that are called indirectly. Here's the example I came up with to reproduce:

defmodule MyApp.MyMod do
  def value do
    1
  end

  def indirect_value do
    value()
  end
end

and here are some assertions I tried to make for it:

defmodule MyApp.MyModTest do
  use ExUnit.Case, async: false
  import Mock

  alias MyApp.MyMod

  test "test" do
    # these two work, obviously
    assert MyMod.value == 1
    assert MyMod.indirect_value == 1

    # in this block, the second assertion fails with:
    # (UndefinedFunctionError) function MyApp.MyMod.indirect_value/0 is undefined (module MyApp.MyMod is not available)
    # I guess this is to be expected, although unintuitive
    with_mock MyMod, [value: fn -> 2 end] do
      assert MyMod.value == 2
      assert MyMod.indirect_value == 2
    end

    # and in this block (adding :passthrough), the second assertion fails, because the indirect_value function still returns 1
    with_mock MyMod, [:passthrough], [value: fn -> 2 end] do
      assert MyMod.value == 2
      assert MyMod.indirect_value == 2
    end
  end
end

I'm not sure how I should use the package to test this kind of calls, if that's at all possible

What you're describing is definitely not the behavior I'd expect either. Unfortunately, I don't have a good solution to this right now...

According to http://elixir-lang.readthedocs.io/en/latest/technical/scoping.html:

Any unbound identifier is treated as a local function call

When we're using the :passthrough flag and call indirect_value, we enter the scope of MyMod and execute the locally scope function since we're not referencing it via the module name. The only workarounds I have to this at the moment are:

  1. Replacing value() with __MODULE__.value()
  2. Mocking indirect_value in your test
  3. Using dependency injection and passing:

Implementation would look like this:

  def indirect_value(value) do
    value.()
  end

Test will look like this:

  assert MyMod.indirect_value(&MyMod.value/0) == 2

All of this being said, I'm going to keep thinking about this for a little while longer to see if we could get the expected behavior to function properly.

I'm having the same exact problem! Would love to see a solution.

same issue here

I've been looking for a solution for this on and off.

The only potential path I've found so far is manually updating the beam file after compilation. There are still a couple hooks I need to jump through to get it working but I'll keep this thread updated.