Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking with Mox Library error: module Myapp.MyModule is not a behaviour, please pass a behaviour to :for

Tags:

mocking

elixir

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.

like image 345
Gabriel Rufino Avatar asked Jan 26 '18 19:01

Gabriel Rufino


People also ask

What is MOX library in Elixir?

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.

Why is my test not calling my mock?

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:

How do I mock the weatherapi behaviour during tests?

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)

What is $callers in MOX?

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.


1 Answers

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).

like image 97
Chris Meyer Avatar answered Sep 22 '22 11:09

Chris Meyer