この記事の概要
- Phoenixのテストで
(Plug.Conn.AlreadySentError) the response was already sent
が出るときはrecycle/1
を試してみる - おそらく1回のテストブロックの中で2回以上getすると発生する?(違うかも
※Phoenix v1.3
問題
Phoenixの勉強がてらシンプルなブログアプリを作ってる際にテストを書いたときに結構ハマったのでシェア
※Guardianというログイン処理周りのライブラリを使っています
テストコード
ex_blog\test\ex_blog_web\controllers\page_controller_test.exs
defmodule ExBlogWeb.PageControllerTest do
use ExBlogWeb.ConnCase
import ExBlog.Support.Utils
alias ExBlog.Accounts
@valid_attrs %{email: "some@example.com", name: "somename", password: "somepassword"}
setup do
{:ok, user} = Accounts.create_user(@valid_attrs)
{:ok, user: user}
end
test "Get /", %{conn: conn, user: user} do
conn = get conn, "/"
assert html_response(conn, 200)
conn =
conn
|> login_user(user)
|> get("/")
# Show <a href="/articles" when login successed
assert html_response(conn, 200) =~ "<a href=\"#{article_path(conn, :index)}\">"
end
end
ちなみにlogin_userはテストヘルパーとして以下のように定義しています
ex_blog\test\support\utils.ex
defmodule ExBlog.Support.Utils do
alias ExBlog.Accounts.User
alias ExBlog.Accounts.Guardian
def login_user(conn, %User{} = user) do
Guardian.Plug.sign_in(conn, user)
end
def logout_user(conn, %User{} = user) do
Guardian.Plug.sign_out(conn, user)
end
end
これを実行すると以下のようなエラーが!
1) test Get / (ExBlogWeb.PageControllerTest)
test/ex_blog_web/controllers/page_controller_test.exs:13
** (Plug.Conn.AlreadySentError) the response was already sent
code: |> login_user(user)
stacktrace:
(plug) lib/plug/conn.ex:1395: Plug.Conn.put_session/3
(guardian) lib/guardian/plug.ex:190: Guardian.Plug.sign_in/5
test/ex_blog_web/controllers/page_controller_test.exs:18: (test)
解決策
公式によるとrecycle/1 を使う必要があるみたいです
クッキー更新などの役割の様子
Phoenix.ConnTest – Phoenix v1.3.4 | recycle/1
以下の例を見ると、postなどでは自動でrecycleしてるみたいなので、自前で使った関数でログインしたのが原因っぽいです
postを使ってログインすれば必要なさそう(未確認)
Phoenix.ConnTest – Phoenix v1.3.4 | module-recycling
実装
ex_blog\test\ex_blog_web\controllers\page_controller_test.exs
defmodule ExBlogWeb.PageControllerTest do
...
test "Get /", %{conn: conn, user: user} do
conn = get conn, "/"
assert html_response(conn, 200)
conn =
conn
|> recycle() # Important!!
|> login_user(user)
|> get("/")
# Show <a href="/articles"> when login successed
assert html_response(conn, 200) =~ "<a href=\"#{article_path(conn, :index)}\">"
end
end
Finished in 0.7 seconds
1 test, 0 failures
余談
以下のようにテストを分けても動きました
クッキーを変化させるようなテスト書く場合は、一回のテストに2回以上getするのが良くないのかな?
describe "get /" do
test "without login", %{conn: conn} do
conn = get conn, "/"
assert html_response(conn, 200) =~ "Simple blog by Phoenix"
end
test "logged in", %{conn: conn, user: user} do
conn =
conn
|> login_user(user)
|> get("/")
# html_responce is \ escaped like \"hogehoge\", not "hogehoge"
# so we need \ before "
assert html_response(conn, 200) =~ "<a href=\"#{article_path(conn, :index)}\">"
end
end