I am trying to use the Mocking Library named Mox in my Elixir project, but even tho following the official documentation: https://hexdocs.pm/mox/Mox.html
I can't define a new behavior for my module functions. It gives me the following error when trying to run the test:
** (ArgumentError) module Myapp.MyModule is not a behaviour, please pass a behaviour to :for (mox) lib/mox.ex:210: Mox.validate_behaviour!/1 (mox) lib/mox.ex:198: Mox.defmock/2 (elixir) lib/code.ex:376: Code.require_file/2 (elixir) lib/enum.ex:678: Enum."-each/2-lists^foreach/1-0-"/2 (elixir) lib/enum.ex:678: Enum.each/2
Here's what I've tried so far:
test_helper.exs:
ExUnit.start()
Mox.defmock(Myapp.MockMyModule, for: Myapp.MyModule)
MyModuleTest.exs
defmodule MyModuleTest do
use ExUnit.Case
import Mox
setup :verify_on_exit!
test "Test status processor in transit with mocked result" do
Myapp.MyModule
|> expect (:put_calculated_eta, fn body, shipment_id, authorization_key -> {:ok, "bla", 200} end)
map = #Some data that fits the function interface
assert {:ok, "bla", 200} == Myapp.MyModule.update_shipment_eta(map)
end
end
What I think is the most weird is that in the documentation it does expect modules to be passed for mocking, but in the error it request for behaviours (which I guess should be functions, which I already tried without success... can anyone tell me why my module cannot be mocked please?
Just for clarification, this mocking is for a response of an external api.
Please let me know if I haven't provided enough info.
Mox is a library for defining concurrent mocks in Elixir. The library follows the principles outlined in "Mocks and explicit contracts" , summarized below: No ad-hoc mocks. You can only create mocks based on behaviours No dynamic generation of modules during tests.
If your mock is called in a different process than the test process, in some cases there is a chance that the test will finish executing before it has a chance to call the mock and meet the expectations. Imagine this:
If you want to mock the WeatherAPI behaviour during tests, the first step is to define the mock with defmock/2, usually in your test_helper.exs , and configure your application to use it: Mox.defmock(MyApp.MockWeatherAPI, for: MyApp.WeatherAPI) Application.put_env(:my_app, :weather, MyApp.MockWeatherAPI)
Instead $callers is used to determine the process that actually defined the expectations. Mox supports global mode, where any process can consume mocks and stubs defined in your tests. set_mox_from_context/0 automatically calls set_mox_global/1 but only if the test context doesn't include async: true. By default the mode is :private.
What's missing, as the error message conveys, is a Behaviour. Elixir Behaviours (note the british spelling) are akin to 'interfaces' in strongly-typed OOP languages; that is, they describe a set of functions which other modules implement.
For example, this module defines a behaviour with a single callback method, do_work/1
, which expects a job in the form of an integer, and returns either {:ok, result}
, or {:error, reason}
:
defmodule WorkBehavior do
@callback do_work(job::integer) :: {:ok, integer} | {:error, atom}
end
And these modules implement that behaviour:
defmodule LazyWorker do
@behaviour WorkBehavior
@impl WorkBehavior
def do_work(job), do: {:error, :too_tired}
end
defmodule HardWorker do
@behaviour WorkBehavior
@impl WorkBehavior
def do_work(job), do: {:ok, job + 42}
end
The end goal of behaviours is to allow you to write modular code, where various implementations can be swapped with ease. With the code above, the worker module can be passed as a parameter, instead of hardcoding it in. Compare:
def closely_coupled do
HardWorker.do_work(12)
end
vs.
def loosely_coupled(worker) do
worker.do_work(12)
end
What Mox is asking you to do is define a Behaviour that captures the API for MyApp.MyModule
, so that Mox can create a fake version for you to test against. The motivation for this approach, versus the more typical dynamic mocking is explained in this post by José Valim.
Without seeing the code for MyApp.MyModule, I can't tell you exactly what code to write, but in general, you should add @callback
tags to it for each method you want to be able to mock. See this page for an intro to typespecs and behaviours (hint: @callback
tags use the typespec syntax, so read the whole page).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With