How to test controller authenticated by Guardian in Elixir/Phoenix
I am using Guardian to authenticate my application but by looking at the documentation I had a hard time figuring out how to test my controllers.
Tagging tests :authenticated
We start out with a test to show a resource. I think this is how it looks straight out of the generators.
test “redirects to show when data is valid”, %{conn: conn} do
conn = post conn, secret_path(conn, :create), plan: @create_attrs assert html_response(conn, 200) =~ “Show Secret”
end
We add some authentication; please look somewhere else for a proper guide on that.
In the end we pipe our secret routes through a authenication pipeline:
pipeline :browser_auth do
plug Guardian.Plug.VerifySession
plug Guardian.Plug.LoadResource
endpipeline :require_authenticated do
plug Guardian.Plug.EnsureAuthenticated, handler: MyErrorHandler
endscope "/", TestAppWeb do
pipe_through [:browser, :browser_auth, :require_authenticated]
resources "/secrets", SecretController
end
And boom, our /secrets urls are no longer accessible without being authenticated.
Running our tests again, and it no longer passes since the show action is no longer accessible. My Error Handler redirects to /login, so we get this error:
1) test update plan redirects when data is valid (TestAppWeb.SecretControllerTest)
test/test_app_web/controllers/secret_controller_test.exs:59
Assertion with == failed
code: assert redirected_to(conn) == secret_path(conn, :show, secret)
left: "/login"
right: "/secrets/203"
stacktrace:
test/test_app_web/controllers/secret_controller_test.exs:61: (test)
To make the connection in the test authenticated we need to add some Guardian logic. Let us start with deciding on how we want it to look in the tests.
We can tag the tests, and have the conn provided by the setup block be authenticated already. Like this:
@tag :authenticted
test “redirects to show when data is valid”, %{conn: conn} do
conn = post conn, secret_path(conn, :create), plan: @create_attrs assert html_response(conn, 200) =~ “Show Secret”
end
To make that tag to anything add this to test/support/conn_case.ex
:
@default_opts [
store: :cookie,
key: "secretkey",
encryption_salt: "encrypted cookie salt",
signing_salt: "signing salt"
]
@signing_opts Plug.Session.init(Keyword.put(@default_opts, :encrypt, false))setup tags do
:ok = Ecto.Adapters.SQL.Sandbox.checkout(Lunch.Repo) unless tags[:async] do
Ecto.Adapters.SQL.Sandbox.mode(Lunch.Repo, {:shared, self()})
end {conn, user} = if tags[:authenticated] do
{:ok, user} = create_user() conn = conn
|> Plug.Session.call(@signing_opts)
|> Plug.Conn.fetch_session
|> Guardian.Plug.sign_in(user, :token)
|> Guardian.Plug.VerifySession.call(%{})
{conn, user}
else
{Phoenix.ConnTest.build_conn(), nil}
end {:ok, conn: conn, user: user}
end
Change create_user to whatever way you have to create a user.
This solution is not perfect. It slows down the tests quite a lot. An alternative might be to bypass the authentication pipeline all together. That will speed up the tests, but the controller will not have a user assigned in the test.