LoginSignup
16
15

More than 5 years have passed since last update.

Phoenix のコントローラーテストでセッションを扱いたい

Last updated at Posted at 2015-09-17

なんか標準で対応してないんですよね。ログインならログインでそこ通してやればいいじゃんみたいな感じの開発者の1人の発言読んだ気がする。こっちはインテグレーションテストがしたいんじゃねーんだよユニットテストがしたいんだよ!と思いました。

これやるのすんごい苦労した。めちゃめちゃ苦労した。stackoverflow なんかにもあったけど、Phoenixの新しいバージョンじゃ通じないみたいでした。サッポロビームの方々、特に @darui_kara 氏にはたっぷりお世話になりました。ありがとうございました。

ではまず、セッションを使ったこんなアクションがあるとします:

web/controllers/user_controller.ex
  def new(conn, _params) do
    tmp_user = get_session(conn, :tmp_user)
    changeset = User.changeset(%User{}, tmp_user)
    render(conn, "new.html", changeset: changeset)
  end

&get_session/2を使っているのでどうにかセッションに値を埋め込みたいですね。
テスト側はmix phoenix.gen.htmlなんかで生成した時はこんな感じだと思います:

test/controllers/user_controller_test.ex
  test "renders form for new resources", %{conn: conn} do
    conn = get conn, user_path(conn, :new)
    assert html_response(conn, 200) =~ "New user"
  end

このままテスト実行すると tmp_user が nil になるのでテスト失敗しますね。テスト側で&put_session/3使いたいですね。この前のアクション実行してからとか、外部の通信とかあったらダルいですね。
そこで、test/support/controller_helper.exというファイルを作ります。あ、YourAppのところは適宜書き換えて下さい。

test/support/controller_helper.ex
defmodule YourApp.ControllerHelper do
  defmacro __using__(args) do
    controller = Keyword.get(args, :controller)

    quote bind_quoted: [controller: controller] do
      use Plug.Test

      @controller controller

      def action(conn, action, params \\ %{}) do
        conn = conn
          |> put_private(:phoenix_controller, @controller)
          |> Phoenix.Controller.put_view(Phoenix.Controller.__view__(@controller))

        apply(@controller, action, [conn, params])
      end

      @session Plug.Session.init(
        store: :cookie,
        key: "_app",
        encryption_salt: "yadayada",
        signing_salt: "yadayada"
      )

      defp with_session_and_flash(conn) do
        conn
        |> Map.put(:secret_key_base, String.duplicate("abcdefgh", 8))
        |> Plug.Session.call(@session)
        |> Plug.Conn.fetch_session()
        |> Phoenix.ConnTest.fetch_flash()
      end
    end
  end
end

雑に解説しますと、conn の初期化処理を自分でしてます。まず、標準で用意されている conn はセッションの準備をしていません。そこで&with_session_and_flash/1というセッション部分の初期化だけを付け足した関数を用意しました。また、標準で用意されているget,post,put,deleteなどのコントローラーへのアクセス関数を通すと conn が再初期化されてしまうので初期化しないでアクセスする関数&action/3を定義しています。

そして、テストファイルでuseを入れてちょちょいと書き足します:

test/controllers/user_controller_test.ex
defmodule YourApp.UserControllerTest do
  use YourApp.ConnCase

  # この行追加
  use YourApp.ControllerHelper, controller: YourApp.UserController

...

  # 中身書き換え
  test "renders form for new resources", %{conn: conn} do
    conn = conn
      |> with_session_and_flash
      |> put_session(:tmp_user, @valid_attrs)
      |> action :new, %{}
    assert html_response(conn, 200) =~ "New user"
  end

はい。put_session,get_session,put_flash,get_flashする前に、with_session_and_flashを入れるのとアクションへのアクセスを独自関数&action/3でやるのがミソです。標準で用意されているget,post,put,deleteなどを使うと conn が作り変えられてセッションが空マップ(%{})になります。ここは涙を飲んで換えて下さい。なあに、アクションもただの関数だからあんま変わりませんって。一応念の為にセッションが必要ないテストでは標準関数を使うことをおすすめします。

できた? できたね? 良かったですね。

16
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
15