xgeek116

xgeek116

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 !

Showing Posts 1 to 10

hubertlepicki

hubertlepicki

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.

xgeek116

xgeek116 OP

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

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

karlosmid

karlosmid

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.

xgeek116

xgeek116 OP

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 ?

crova

crova

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

xgeek116

xgeek116 OP

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"}
xgeek116

xgeek116 OP

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

trisolaran

trisolaran

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
dorgan

dorgan

Or you could just use Patch :wink:

xgeek116

xgeek116 OP

@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

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
ryanwinchester
apply_graft/2 doesn’t rewrite an add_many sub-workflow’s deps on an add step. Grafted jobs cancel with “upstream job was deleted” Version...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews