Fl4m3Ph03n1x

Fl4m3Ph03n1x

How to mock HTTPoison with Mox?

Background

I have library that uses HTTPoison for some functionality I need to test. To achieve this I am using Mox, which I believe is the universal mocking library for Elixir (even though there are others this one has the seal of approval of José Valim)

Problem

Everything is going fine, I define my mocks in the test_helpers.exs:

    ExUnit.start()
    Mox.defmock(HTTPoisonMock, for: HTTPoison)

And I set up my dummy tests:

defmodule Alfred.Test.Http.Test do
  use ExUnit.Case, async: true
  import Mox
  # Make sure mocks are verified when the test exits
  setup :verify_on_exit!

  describe "get" do
    test "works on OK" do
      HTTPoisonMock
      |> get(:get, fn _ -> 1 end)

      assert HTTPoisonMock.get(1) == 1
    end
  end
end

The problem here is I can’t run them:

module HTTPoison is not a behaviour, please pass a behaviour to :for

Mock Contracts, not implementations

Now, I know José Valim supports this ideology, thus everything we should mock should have a contract. But HTTPoison is not mine and it doesn’t have one. So this brings me to the following question:

  • How can I mock 3rd libraries that don’t offer behaviours using Mox ?

Marked As Solved

bonf

bonf

You can wrap HTTPoison with your own module.

This works for me:

define a behaviour

defmodule MyApp.Http.Adapter do
  @callback get(url :: String.t(), headers :: list) :: String.t()

  @callback post(url :: String.t(), body :: String.t(), headers :: list) :: String.t()
end

define the real implementation

defmodule MyApp.Http.Client do

  def get(url, headers) do
    response = HTTPoison.get(.....)
    ...
  end

  def post(url, body, headers) do
    ...
  end

in config/test.exs

config :my_app, :http_adapter, MyApp.Http.Mock

for dev/prod

config :my_app, :http_adapter, MyApp.Http.Client

usage:

defmodule MyApp.Foo do
  @http_client Application.get_env(:my_app, :http_adapter)

  def bar() do
      @http_client.post(url, payload, [])
  end
end

in your test:

    MyApp.Http.Mock
    |> expect(:post, fn _url, _body, _headers ->
      {:ok, %HTTPoison.Response{status: 200, body: ....}}
    end)

Hope this helps.

Also Liked

sgyyz

sgyyz

As you can see HTTPoison it uses HTTPoison.Base directly. And HTTPoison.Base defines behaviors. Which means we can mock HTTPoison.Base instead of mock HTTPoison.

# config/config.exs
config :module_name, :httpoison, HTTPoision

# config/test.exs
config :module_name, :httpoison, MockHTTPoison

# test/support/mocks.ex
Mox.defmock(ModuleName.MockHTTPoison, for: HTTPoison.Base)

Reference:

peerreynders

peerreynders

If your test suite doesn’t break when you change HTTP client (and by default, when you change the contract the HTTP client is using) then it means you are not testing it at all.

I think there is a bit of misunderstanding here. The intent that I see behind that statement is that the HTTP client library is treated as an implementation detail. So there really are two approaches:

  • When testing, simply leave the HTTP client library in place. In a way this approach is similar to testing against a working, running database.

  • Establish a boundary between your code and the HTTP client library. I.e you design the contract while being constrained by the capabilities of the library.

Ultimately the second approach is consistent with Rainsberger’s “Narrowing API/Pattern of Usage API”. So in that case you would have your own HTTP client behaviour module which acts as the interface for the Happy Zone while the callback module interfaces with HTTPPoison in the DMZ to complete the DMZ adapter.

For your micro tests you use a faux callback module when performing collaboration tests. Much less frequently you run contract tests with the real callback module and HTTP client library to show that the contract implementation is still behaving in the expected way (Clearing Up the Integrated Tests Scam).

So now we’re just back at to the post that you’ve already marked as the solution.

peerreynders

peerreynders

Interestingly that article links to a google groups topic:

Your integration tests should not mock HTTPoison calls. A very simple way to see this is: if you replace HTTPoison by another HTTP client, should your integration test suite break? Probably not, your app behaviour is still be the same.

:slightly_smiling_face:

Where Next?

Popular in Questions Top

Kurisu
For example for a current url like http://localhost:4000/cosmetic/products?_utf8=✓&query=perfume&page=2, I would like to get: ...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

Other popular topics Top

Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New
johnnyicon
Hi all, I’ve just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I’m trying to use Postgres...
New
hariharasudhan94
lets say i have a sample like a = 20; b = 10; if (a > b) do {:ok, "a"} end if (a < b) do {:ok, b} end if (a == b) do {:ok, "equa...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I forese...
New
Qqwy
Update: How to use the Blogs & Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 126479 1222
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New
dogweather
I wrote this comment on r/haskell, and it’s not popular there. :wink: But I think I’m on to something… Haskell reminds me of Java, and e...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New

We're in Beta

About us Mission Statement