【发布时间】:2018-09-01 20:00:18
【问题描述】:
我很难对失败的测试进行故障排除,其中 conn.assigns 都是同一测试语句的一部分,但在测试的两行之间被清空。
我正在阅读“Programming 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 指令来检查获取请求之前和之后的连接。在“获取之前:”日志中,我仍然有一个带有 :current_user 键的 conn.assigns,但在随后的“获取之后”日志中,它消失了。
我最初认为我的身份验证机制对此负责,但后来我创建了一个日志插件来检查管道,并且我可以看到从调用 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 则通过认证。设置 login_user 就是这样做的,在 :current_user 键下的 conn.assigns 中注入一个用户。在我的测试中,无论出于何种原因,由于 conn.assigns 在 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
在授权控制器中调用函数:
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
【问题讨论】:
-
你能把
login_user的代码贴出来吗?这可能与这个问题有关:stackoverflow.com/q/42347723/320615. -
我添加了 login_user 函数,以及来自我的 Auth 控制器的调用函数。除非我遗漏了某些东西,否则“条件”应该确保如果设置了 :current_user,我不会覆盖我的分配。
-
在
user = insert_user(username: "max")之后添加另一个检查 - 你真的在这里有一个用户,还是只有一个nil?