Search code examples
unit-testingsessionphoenix-frameworkflash-message

Accessing session and flash when unit-testing a custom plug


I have a login requirement plug that pretty much resembles the one described in this example by Thoughtbot. I want to add a flash notification when the user is redirected. It works in the browser, but not when testing it in isolation.

The plug:

# In webs/plugs/require_login.ex
defmodule MyApp.Plugs.RequireLogin do
  import Plug.Conn

  def init(opts), do: opts
    if false # real user authentication omitted
      conn
    else
      conn
      |> Phoenix.Controller.put_flash(:error, "Login required.")
      |> Phoenix.Controller.redirect(to: "/")
      |> halt
    end
  end
end

The test used for this:

defmodule MyApp.Plugs.RequireLoginTest do
  use MyApp.ConnCase

  test "user is redirected when authentication fails" do
    conn = conn |> MyApp.Plugs.RequireLogin.call(%{})

    assert Phoenix.Controller.get_flash(conn, :error) == "Login required."
    assert redirected_to(conn) == "/"
  end
end

The error message I get is:

(ArgumentError) flash not fetched, call fetch_flash/2

The error occurs in the plug module but if I comment out the put_session line there, the error moves to my test file.

I understand the session store is configured in lib/my_app/endpoint.ex but how can I re-use this configuration so that I can unit-test plugs?

Here's how the plug is hooked into the router:

# web/router.ex
pipeline :browser do
  # the Phoenix default
end

scope "/", MyApp do
  pipe_through [:browser, MyApp.Plugs.RequireLogin]
  resource "/protected", MyController, only: [:index]
end

Solution

  • I was able to solve this using bypass_through. I did not "fire" a request using e.g. Phoenix.ConnTest.get/3, resulting in the plug pipeline not being called. As soon as I added get/3, the flash hash became available.

    defmodule MyApp.Plugs.RequireLoginTest do
      use MyApp.ConnCase
    
      test "user is redirected when authentication fails", %{conn: conn} do
        conn = conn
               |> with_pipeline
               |> MyApp.Plugs.RequireLogin.call(%{})
    
        assert Phoenix.Controller.get_flash(conn, :error) == "Login required."
        assert redirected_to(conn) == "/"
      end
    
      defp with_pipeline(conn) do
        conn
        |> bypass_through(MyApp.Router, [:browser])
        |> get("/protected-uri") # any uri, bypass_through ignores the router rules
      end
    end