在对自定义插件进行单元测试时访问会话和闪存

Accessing session and flash when unit-testing a custom plug

我有一个登录要求插件,它与 this example by Thoughtbot 中描述的插件非常相似。我想在重定向用户时添加一个 flash 通知。它在浏览器中有效,但在单独测试时无效。

插头:

# 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

用于此的测试:

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

我收到的错误信息是:

(ArgumentError) flash not fetched, call fetch_flash/2

错误发生在插件模块中,但如果我在那里注释掉 put_session 行,错误就会转移到我的测试文件中。

我知道会话存储是在 lib/my_app/endpoint.ex 中配置的,但是我怎样才能重新使用这个配置以便我可以对插件进行单元测试?

插头连接路由器的方式如下:

# 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

编辑
我首先想到的是您在测试中缺少参数。

您的 conn 可能是在您的 ConnCase 中某处生成的:

setup tags do
  ...

  {:ok, conn: Phoenix.ConnTest.build_conn()}
end

你的测试应该是这样的:

test "user is redirected when authentication fails", %{conn: conn} do

能够使用这个conn。



第二件事是你的测试中没有请求(get/post 等),你的连接可能没有通过 :browser 管道。

我使用 bypass_through 解决了这个问题。我没有 "fire" 使用例如Phoenix.ConnTest.get/3,导致插件管道没有被调用。我一添加 get/3,闪存哈希就可用了。

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