在测试过程中丢失conn.assigns

时间:2017-09-22 11:07:23

标签: elixir phoenix-framework

我很难对失败的测试进行故障排除,其中conn.assigns既是同一测试语句的一部分,又在测试的两行之间清空。

我正在阅读"编程Phoenix",并重新编写代码以使其与Phoenix 1.3一起使用。在3个测试中,存储在conn.assigns中的:current_user在测试过程中丢失,例如在删除和获取控制器测试之间。

测试代码为:

  describe "delete video" do
    setup [:login_user, :create_video]

    test "deletes chosen video", %{conn: conn, video: video} do
      conn = delete conn, video_path(conn, :delete, video)
      assert redirected_to(conn) == video_path(conn, :index)
      assert_error_sent 404, fn ->
        Logger.warn("Before get: #{inspect(conn.assigns)}")
        conn = get conn, video_path(conn, :show, video)
        Logger.warn("After get: #{inspect(conn.assigns)}")
        conn
      end
    end
  end

我添加了Logger.warn指令来检查get请求之前和之后的连接。在"之前得到:" log我仍然有一个conn.assigns:current_user key,但在随后的"之后得到"记录,它已经消失了。

我最初认为我的身份验证机制负责这一点,但我创建了一个Logging Plug来检查管道,我可以看到,从管道的开始调用get,current_user从分配中丢失。

这是测试输出,显示管道开始/结束时的日志记录以及上面测试的日志。

ubuntu@ubuntu-xenial:~/rumbl$ MIX_ENV=test mix test test/rumbl_web/controllers/video_controller_test.exs:98
[info] Already up
Including tags: [line: "98"]
Excluding tags: [:test]

warning: module attribute @update_attrs was set but never used
  test/rumbl_web/controllers/video_controller_test.exs:10

[debug] QUERY OK db=1.2ms
begin []
[debug] QUERY OK db=2.7ms
INSERT INTO "users" ("name","password_hash","username","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5) RETURNING "id" ["Some User", "$2b$12$0VvTPna8JMdSXcbNz2uvwOdhYWF/3ibhQ.gntdXqsb8v1TSeCZ0.K", "max", {{2017, 9, 22}, {10, 56, 53, 527902}}, {{2017, 9, 22}, {10, 56, 53, 529912}}]
[debug] QUERY OK db=0.1ms
commit []
[debug] QUERY OK db=0.1ms
begin []
[debug] QUERY OK db=1.1ms
INSERT INTO "videos" ("description","title","url","user_id","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5,$6) RETURNING "id" ["some description", "some title", "some url", 351, {{2017, 9, 22}, {10, 56, 53, 544069}}, {{2017, 9, 22}, {10, 56, 53, 544075}}]
[debug] QUERY OK db=0.1ms
commit []
[info] DELETE /manage/videos/274
[warn] Start of pipeline: %{current_user: %RumblWeb.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">, id: 351, inserted_at: ~N[2017-09-22 10:56:53.527902], name: "Some User", password: "supersecret", password_hash: "$2b$12$0VvTPna8JMdSXcbNz2uvwOdhYWF/3ibhQ.gntdXqsb8v1TSeCZ0.K", updated_at: ~N[2017-09-22 10:56:53.529912], username: "max", videos: #Ecto.Association.NotLoaded<association :videos is not loaded>}}
[warn] End of pipeline: %{current_user: %RumblWeb.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">, id: 351, inserted_at: ~N[2017-09-22 10:56:53.527902], name: "Some User", password: "supersecret", password_hash: "$2b$12$0VvTPna8JMdSXcbNz2uvwOdhYWF/3ibhQ.gntdXqsb8v1TSeCZ0.K", updated_at: ~N[2017-09-22 10:56:53.529912], username: "max", videos: #Ecto.Association.NotLoaded<association :videos is not loaded>}}
[warn] Start of authenticate_user: %{current_user: %RumblWeb.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">, id: 351, inserted_at: ~N[2017-09-22 10:56:53.527902], name: "Some User", password: "supersecret", password_hash: "$2b$12$0VvTPna8JMdSXcbNz2uvwOdhYWF/3ibhQ.gntdXqsb8v1TSeCZ0.K", updated_at: ~N[2017-09-22 10:56:53.529912], username: "max", videos: #Ecto.Association.NotLoaded<association :videos is not loaded>}}
[debug] Processing with RumblWeb.VideoController.delete/2
  Parameters: %{"id" => "274"}
  Pipelines: [:browser, :authenticate_user]
[debug] QUERY OK source="videos" db=0.9ms
SELECT v0."id", v0."description", v0."title", v0."url", v0."user_id", v0."category_id", v0."inserted_at", v0."updated_at" FROM "videos" AS v0 WHERE (v0."user_id" = $1) AND (v0."id" = $2) [351, 274]
[debug] QUERY OK db=0.1ms
begin []
[debug] QUERY OK db=0.3ms
DELETE FROM "videos" WHERE "id" = $1 [274]
[debug] QUERY OK db=0.0ms
commit []
[info] Sent 302 in 46ms
[warn] Before get: %{current_user: %RumblWeb.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">, id: 351, inserted_at: ~N[2017-09-22 10:56:53.527902], name: "Some User", password: "supersecret", password_hash: "$2b$12$0VvTPna8JMdSXcbNz2uvwOdhYWF/3ibhQ.gntdXqsb8v1TSeCZ0.K", updated_at: ~N[2017-09-22 10:56:53.529912], username: "max", videos: #Ecto.Association.NotLoaded<association :videos is not loaded>}}
[info] GET /manage/videos/274
[warn] Start of pipeline: %{}
[warn] End of pipeline: %{current_user: nil}
[warn] Start of authenticate_user: %{current_user: nil}
[info] Sent 302 in 2ms
[warn] After get: %{current_user: nil}


  1) test delete video deletes chosen video (RumblWeb.VideoControllerTest)
     test/rumbl_web/controllers/video_controller_test.exs:98
     expected error to be sent as 404 status, but response sent 302 without error
     code: assert_error_sent 404, fn ->
     stacktrace:
       (phoenix) lib/phoenix/test/conn_test.ex:600: Phoenix.ConnTest.assert_error_sent/2
       test/rumbl_web/controllers/video_controller_test.exs:101: (test)



Finished in 0.6 seconds
8 tests, 1 failure, 7 skipped

Randomized with seed 982730

身份验证机制遵循本书的建议,如果conn.assigns中有:current_user,则通过身份验证。 setup login_user就是这样做的,在conn.assigns中注入一个用户:current_user key。在我的测试中,无论出于何种原因,由于conn.assigns在get请求中的某个时刻为空,因此身份验证失败,并且用户被重定向到主页。

我不确定如何解决delete语句和后续get之间发生的问题。我查看了管道,但是在日志语句中显示conn.assigns在进入管道之前被清空。我期待conn在整个测试声明中不会改变,也许这是一个错误的假设。

回复Dogbert的评论:

控制器测试中的login_user函数:

  defp login_user(_) do
    user = insert_user(username: "max")
    conn = assign(build_conn(), :current_user, user)
    {:ok, conn: conn, user: user}
  end

auth控制器中的调用功能:

  def call(conn, repo) do
    user_id = get_session(conn, :user_id)

    cond do
      user = conn.assigns[:current_user] ->
        conn
      user    = user_id && repo.get(RumblWeb.User, user_id) ->
        assign(conn, :current_user, user)
      true ->
        assign(conn, :current_user, nil)
    end
  end

1 个答案:

答案 0 :(得分:5)

这是由凤凰回收造成的,请参阅(https://hexdocs.pm/phoenix/1.3.0/Phoenix.ConnTest.html#module-recycling

回收

浏览器使用cookie实现存储。在响应中设置cookie时,浏览器会将其存储并在下一个请求中发送。

为模拟此行为,此模块提供了回收的概念。 recycle / 1函数接收连接并返回一个新连接,类似于conn / 0返回的连接,其中来自先前连接的所有响应cookie被定义为请求标头。这在测试需要cookie或会话工作的多个路由时非常有用。

请记住,Phoenix会自动回收调度之间的连接。这通常很有效,但如果您在下次调度之前修改连接,它可能会丢弃信息:

# No recycling as the connection is fresh
conn = get build_conn(), "/"

# The connection is recycled, creating a new one behind the scenes
conn = post conn, "/login"

# We can also recycle manually in case we want custom headers
conn =
  conn
  |> recycle()
  |> put_req_header("x-special", "nice")

# No recycling as we did it explicitly
conn = delete conn, "/logout"

回收还会回收“接受”标题。

要保留assigns,您可以在对deleteget的调用之间手动回收和分配:

saved_assigns = conn.assigns
conn = 
  conn
  |> recycle()
  |> Map.put(:assigns, saved_assigns)