Troubles using Mox library for testing when no Mox expectations are defined

There is the Recaptcha library

which helps me in verifying recaptcha responses. All nice. Time for testing (yeah, after getting code to work somehow – apologies to all TDD fans). Obviously I don’t want to hit uncle google with my tests, so:

Mox.defmock(MyApplication.Accounts.MockRecaptcha, for: MyApplication.Accounts.RecaptchaBehaviour)

inside test_helper.ex. Needed to define that behaviour separately:

defmodule MyApplication.Accounts.RecaptchaBehaviour do
	@callback verify(String.t(), Keyword.t()) :: {:ok, Response.t()} | {:error, [atom]}
	@callback verify(String.t()) :: {:ok, Response.t()} | {:error, [atom]}
end

do some tests using:

MyApplication.Accounts.MockRecaptcha
|> expect(:verify, fn _response -> {:ok, _response} end)

So far so good, except… all other tests are now failing with:

** (Mox.UnexpectedCallError) no expectation defined for MyApplication.Accounts.MockRecaptcha.verify/1 in process #PID<0.854.0> with args [nil]

Reading the fine docs I find: “[…] you might want the implementation to fall back to a stub (or actual) implementation when no expectations are defined. stub_with/2 is just what you need!”

So another line in test_helper.ex:

Mox.stub_with(MyApplication.Accounts.MockRecaptcha, Recaptcha)

That doesn’t work because ** (ArgumentError) Recaptcha does not implement any behaviour, Well… let’s add my own “proxy” then, which does:

defmodule MyApplication.Accounts.Recaptcha do
	@behaviour MyApplication.Accounts.RecaptchaBehaviour

	def verify(response, options \\ []) do
		Recaptcha.verify(response, options)
	end
end

And change the test_helper.ex line to

Mox.stub_with(MyApplication.Accounts.MockRecaptcha, MyApplication.Accounts.Recaptcha)

Now the ArgumentError is gone but all tests with no Mox expectations fail the same as before. No change with and without the stub_with/2.

And I feel like I spent already far too much time with it… :frowning: Any help to put me on track?

1 Like

Since nobody seems to be able to step-in I added a

TL;DR

Unrelated tests fail because “no expectation defined” when using Mox library and stub_with/2 doesn’t seem to be of any help

and posted this question on SO:

1 Like

I am by no means an expert on mocks, but recently I have implemented a few. I found this article super helpful. Specifically he talks about using environments to replace the module, in your case, ReCapthca, in the tests.

So in the modules where the recaptcha is called you would replace Recaptcha.verify(response, options) with:

recaptcha().verify(response, options)
...
#getting the actual Recaptch module from the environment
defp recaptcha do 
   Application.get_env(:my_app, :reacaptcha)
end 

Then in your test_helper.exs under the mock definition you can add:
Application.put_env(:my_app, :recaptcha, MyApplication.Accounts.MockRecaptcha).

Remember to add: config :my_app, :recaptcha, MyApplication.Accounts.Recaptcha to your config.exs so the Recaptcha works in all other environments.

That should enable the Mox expect/2 to work in the test.

I’m not sure about the Mox.stub_with/2 function in the test_helper, I haven’t played with Mox stubs.

I have a telegram bot client in my app which uses both mocks and stubs during tests and it seems to work fine. Here’s it’s setup:

config/test.exs

config :app, App.Bot, adapter: MockBot

lib/bot.ex

defmodule App.Bot do
  # I'm using compile-time configuration
  # https://hexdocs.pm/mox/Mox.html#module-compile-time-requirements
  # it's probably not important
  @adapter Application.compile_env!(:app, [__MODULE__, :adapter])
  
  def set_webhook(url) do
    @adapter.set_webhook(url)
  end
  
  # ...
end

lib/bot/adapter.ex

defmodule App.Bot.Adapter do
  @moduledoc false
  @callback set_webhook(url :: String.t()) :: :ok | {:error, map}
  # ...
end

test/support/mocks.exs

Mox.defmock(MockBot, for: App.Bot.Adapter)

test/support/stub_bot.ex

defmodule StubBot do
  @behaviour App.Bot.Adapter

  @impl true
  def set_webhook(_url), do: :ok

  # ...
end

and finally test/support/data_case.ex and other helper cases for tests that end up touching the bot module have Mox.stub_with/2 in setup:

defmodule App.DataCase do
  use ExUnit.CaseTemplate

  # ...

  setup tags do
    # ...

    Mox.stub_with(MockBot, StubBot)

    # ...
  end
end
2 Likes

Thank you for sharing. Do you have anything else (like tests in other files) hitting the code of your @App.Bot.Adapter? Or – IOW – if you remove the stub_with do you get the same error(s) as I do (about “no expectation defined”) from other test files?

Do you have anything else (like tests in other files) hitting the code of your @App.Bot.Adapter ?

Yes, the bot logs “important” messages, so the stub is executed somewhat often in tests.

expect/2 works as… well… expected. The problem is with unrelated tests that now fail because of “no expectation defined”, for which the stub_with/2 is supposed to be a cure

And – just to make sure – you didn’t have to adapt/modify any of the other test files, did you?

Are asking if I had to add Mox.expect everywhere?

Or anything for that matter, that would make them work normally.

Yet I was writing this at the same time while you were checking whether removing the stub_with/2 from test_halper.ex makes any difference. And if I add the stub_with/2 to e.g. conn_case.ex then test files, which use MyApplication.ConnCase pass (!). And I guess that if you left the line in test_helper.ex and removed it from data_case.ex you’d encounter the same issue I have. If that’s the case then - WTH? Test files “see” the defmock but don’t “see” the stub_with defined in the very same (test_helper.ex) file?

1 Like

Right - if I put the stub_with into both conn_case and data_case then all tests work normally. IOW – @ruslandoga – you’re my saviour, man! I guess it has to be invoked always in the given test file context. If I now re-read the doc, the wording might actually imply this… gosh!

2 Likes

If possible please put this one back - as this was the actual solution, and I’d like to mark it as such. Thank you once more.

1 Like