Mox is ignoring my Mocked module and calling the real function

I’m using using mox to help me with my unit testing (for now, I want to mock a function that fetches external API).

my mix.exs :
{:mox, "~> 1.0", only: :test}

My test_helper.exs :

ExUnit.start()

Mox.defmock(MyApp.MockExternalSources, for: MyApp.MyModule.ExternalSources)

My module code (MyApp.MyModule.ExternalSources) :

defmodule MyApp.MyModule.ExternalSources do

    @callback http_get_resource(url :: String.t(), headers :: list()) :: {:ok, map()} | :error
    @callback fetch_one_external_resource(params :: map()) :: map()
  
    require Logger
  
    def fetch_one_external_resource(params) do
      with {:ok, data} <-
        http_get_resource(params["url"], params["headers"]) do
        do_something(data)
      else
        :error -> %{}
      end
    end
  
    def http_get_resource(url, headers) do
      case HTTPoison.get(url, headers) do
        {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
          {:ok, Poison.decode!(body)}
  
        {:ok, %HTTPoison.Response{status_code: 404}} ->
          :error
  
        {:error, %HTTPoison.Error{reason: _reason}} ->
          :error
      end
    end
  end

And my test module :

defmodule MyApp.MyModule.ExternalSources.Test do
  use ExUnit.Case, async: true
  import Mox

  setup :verify_on_exit!

  test "fetch and process external data" do

    MyApp.MockExternalSources
    |> expect(:http_get_resource, fn _url, _headers ->
      {:ok, %{status_code: 200, body: %{key1: "value1", key2: "value2"}}}
    end)

    params = %{
      "url" => "xxx",
      "headers" => "yyy"
    }

    assert MyApp.MyModule.ExternalSources.fetch_one_external_resource(params) ==
             %{"new_key1" => "value1", "new_key2" => "value2"}
  end
end

But I always get a failed assertion and the behaviour is that the right value (MyApp.MyModule.ExternalSources.fetch_one_external_resource(params)) is actually calling the external API and getting the real values ignoring my static data in the mock (%{key1: "value1", key2: "value2"})

Any help please !

Did you clear / trigger application rebuilt after introducing the Mox mock? I think I was having similar problem and I had to trigger application recompilation, by either mix clear or even removing the whole _build dir and rebuilding it from scratch.

Hi @hubertlepicki , yes I did but the same result.

I think there is something missing (an implementation or something ?) to detect the mocked function

Hi!
You are missing env variables.
In test_helpers you need to put
Application.put_env(:app_name, :external, MyApp.MockExternalSources)
and in test you need to call it in this way:

Application.get_env(:app_name, :external).fetch_one_external_resource

and mock in test fetch_one_external_resource function instead of http_get_resource.

I suggest that you refactor your MyApp.MyModule.ExternalSources so that http_get_resource methods is implemented in new module.

This way you separate your business logic from contacting external resources.

Hi @karlosmid ,

I have this error :
** (Mox.UnexpectedCallError) no expectation defined for MyApp.MockExternalSources.fetch_one_external_resource/1 in process #PID<0.529.0> with args

Maybe I didn’t get it right, can you please help me add your proposition to my code ?

Mox was expecting fetch_one_external_resource/1 as the name of the function to be called, but your expectation is calling another function.

Yes because I mocked the http_get_resource/2 which does the HTTP call, I just need to simulate that behaviour not the entire fetch_one_external_resource/1 function

Can anyone please show me what’s wrong ?

Here is the updates that I did based on @karlosmid answer.

in test_helper.ex :

I added ;
Application.put_env(:app_name, :external, MyApp.MockExternalSources)

in my test file I updated the assert to :

assert Application.get_env(:app_name, :external).fetch_one_external_resource(params) ==
             %{"new_key1" => "value1", "new_key2" => "value2"}

If I mock fetch_one_external_resource there will not be a real test, It will be a static verification of the result

The core problem is that it’s not enough to create a mock from a behaviour, you should also tell your implementation to use it in tests. Right now, your fetch_one_external_resource is always calling http_get_resource . The implementation is fixed.

As others have already pointed out:

  1. Refactor http_get_resource into its own module. Let’s call it “APIClient”
  2. Have ExternalSources retrieve which APIClient module to use vie config. By default it would be APIClient, in the test environment it will be APIClientMock
  3. Define APIClientMock in thests with defmock and set the desired expectations in your test

Or you could just use Patch :wink:

1 Like

@trisolaran , here is all the updates that I did by I always get an empty left in assertion (left: %{})

In ExternalSources module, I added :

@api_client Application.compile_env!(:app_name, :api_client)

The call to the http_get_resource became :
@api_client.http_get_resource(url, headers)

In test_helper.exs I deleted the lines :

Mox.defmock(MyApp.MockExternalSources, for: MyApp.MyModule.ExternalSources)

and
Application.put_env(:app_name, :external, MyApp.MockExternalSources)

In my test file, I added :

  test "fetch and process external data" do
    Mox.defmock(MyApp.MyModule.APIClientMock,
      for: MyApp.MyModule.APIClient
    )

    MyApp.MyModule.APIClientMock
    |> expect(:http_get_resource, fn _url, _headers ->
      {:ok, %{status_code: 200, body: %{key1: "value1", key2: "value2"}}}
    end)

...

assert MyApp.MyModule.ExternalSources.fetch_one_external_resource(params) ==
             %{"newkey1" => "value1", "newkey2" => "value2"}

In my config.exs :

config :app_name, api_client: MyApp.MyModule.APIClient

In my test.exs :

config :app_name, api_client: MyApp.MyModule.APIClientMock

The Mox documentation has a complete example

Yes, I tried it. I put my current setup in my previous comment, If you have an idea please let me know.

From the example you’ve given here it theoretically should work as you expect. Important to note is that you need to ensure that the mock calling code needs to be part of the same process as the mock defining code.

There’s also a library to deal with injecting mocks into your application code. It’s fairly opinionated but maybe you like it: knigge.

Nope. His implementation is always calling the real implementation because the call to http_get_resource is hard-coded. Check the body of fetch_one_external_resource.

He just needs to follow the example in the docs and do something like:

def fetch_one_external_resource(params) do
      with {:ok, data} <-
        api_module().http_get_resource(params["url"], params["headers"]) do
        do_something(data)
...

defp api_module(), do: Application.get_env(:my_app, :api_module, MyApp.MyModule.ExternalSources )

And add:

Application.put_env(:my_app, :api_module, MyApp.MockExternalSources)

to test_helper.exs or the test config.

It’s all in the example, really. Or maybe use a whole different library :man_shrugging:

I’m referring to this part of his response

In ExternalSources module, I added :

@api_client Application.compile_env!(:app_name, :api_client)

The call to the http_get_resource became :
@api_client.http_get_resource(url, headers)