automated-testselixirphoenix-frameworkmox

Elixir Mox library testing Phoenix code - with no Mox expectations in some test files


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.


Solution

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