jjh42 / mock

Mocking library for Elixir language
MIT License
652 stars 81 forks source link

Not working, at least not in the way I expected #71

Closed naps62 closed 11 months ago

naps62 commented 7 years ago

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

Olshansk commented 7 years ago

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.

afjackman commented 7 years ago

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

keown commented 6 years ago

same issue here

Olshansk commented 6 years ago

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.

acarvajal-gap commented 6 years ago

+1

messutied commented 5 years ago

+1

dxnxk-0 commented 1 year ago

+1

gaffneylg commented 1 year ago

Having the same issue here, has there been any solutions or workarounds found?