1

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

Details:

There is the Recaptcha library

https://github.com/samueljseay/recaptcha

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... :-( Any help to put me on track?

Update:

As requested in the comments, the failing tests are for example controller tests:

    describe "guest GET /signup" do
        setup do
            System.put_env("RECAPTCHA_SITE_KEY", "123")
            {:ok, conn: get(build_conn(), "/signup")}
        end

        test "returns HTTP_OK", %{conn: conn} do
            assert response(conn, 200)
        end

        test "invokes UserView", %{conn: conn} do
            assert Phoenix.Controller.view_module(conn) == MyApplication.UserView
        end

        test "renders into guest layout", %{conn: conn} do
            assert Phoenix.Controller.layout(conn) == {MyApplication.LayoutView, :guest_layout}
        end

        test "renders 'new' template", %{conn: conn} do
            assert Phoenix.Controller.view_template(conn) == "new.html"
        end
    end

Yes, due to the request being generated they (unnecessarily) "touch" the Recaptcha, which is a different thing and the "solution" is not to make them walk around the Recaptcha but rather to make stub_with/2 do its job.

silverdr
  • 1,978
  • 2
  • 22
  • 27
  • Can you include your test code? It's hard to see how these fit together. And can you specify whether or not you are running tests asynchronously? The recaptcha package really should be allowing the HTTP client to be specified as an optional override (because it is set at compile time, making it almost impossible to test). – Everett Oct 01 '21 at 00:39
  • Does the call to the moxed function happen in the same process as the test, or in a process being spawned (Task, etc..)? If the process is different, this might explain what you encounter? https://hexdocs.pm/mox/Mox.html#module-multi-process-collaboration Another reason could be a number of calls greater than 1, since by default [expect](https://hexdocs.pm/mox/Mox.html#expect/4)) sets `n` to `1`? – sabiwara Oct 01 '21 at 01:09
  • @Everett - the failing tests are in separate file(s) like e. g. a typical controller tests, asserting HTTP status code, rendering a view, etc. – silverdr Oct 01 '21 at 07:35
  • 1
    @sabiwara - it's a simple `mix test` and I don't do any process spawning in the test files either. BTW - it seems that with help from an elixirforum member, I am now close to a solution – silverdr Oct 01 '21 at 07:39
  • It would be helpful to see the code in your "typical controller tests" -- SO is better suited to evaluating actual code. – Everett Oct 01 '21 at 15:07
  • @Everett - Controller tests code added as requested – silverdr Oct 04 '21 at 17:18
  • That's helpful, but I still don't see where you are calling `expect/2` or where exactly your controllers are calling `verify` on `Recaptcha` (or your proxy). It sounds like all tests are using the same instance (?) here, so I think including those details in your post will help us see where the wires are crossed. – Everett Oct 04 '21 at 19:44

1 Answers1

1

As discussed on Elixir Forum stub_with/2 cannot be called "globally" like e. g. from test_helper.ex. It has to be called only either in setup or directly in the test.

silverdr
  • 1,978
  • 2
  • 22
  • 27