【问题标题】:Phoenix controller test, set request host凤凰控制器测试,设置请求主机
【发布时间】:2016-03-02 18:19:02
【问题描述】:

我正在制作一个多站点应用程序。我想在测试控制器之前在连接上设置请求主机。在 Rails 中,我们可以使用

before :each do
  request.env["HTTP_REFERER"] = '/'
end

有人可以建议如何在凤凰城做同样的事情吗?

编辑 1:我可以设置主机使用 conn |> put_req_header("host", "abc.com"),但这并没有改变conn 对象中的host 属性。它仍然指向“www.example.com”

编辑 2:我也试过了

test "creates resource and redirects when data is valid", %{conn: _conn} do
  struct_url = %{Myapp.Endpoint.struct_url | host: "abc.com"}
  conn = post(conn, registration_url(struct_url, :create, user: @valid_attrs))
  assert redirected_to(conn) == "/"
end

但我收到以下错误:

$ mix test test/controllers/registration_controller_test.exs                                                                                 1) test creates resource and redirects when data is valid (Myapp.RegistrationControllerTest)
     test/controllers/registration_controller_test.exs:14
     ** (RuntimeError) expected action/2 to return a Plug.Conn, all plugs must receive a connection (conn) and return a connection
     stacktrace:
       (myapp) web/controllers/registration_controller.ex:1: Myapp.RegistrationController.phoenix_controller_pipeline/2
       (myapp) lib/phoenix/router.ex:255: Myapp.Router.dispatch/2
       (myapp) web/router.ex:1: Myapp.Router.do_call/2
       (myapp) lib/myapp/endpoint.ex:1: Myapp.Endpoint.phoenix_pipeline/1
       (myapp) lib/phoenix/endpoint/render_errors.ex:34: Myapp.Endpoint.call/2
       (phoenix) lib/phoenix/test/conn_test.ex:193: Phoenix.ConnTest.dispatch/5
       test/controllers/registration_controller_test.exs:16

registration_controller.ex 第 1 行是 defmodule Myapp.RegistrationControllerTest do

编辑 3: 创建registration_controller.ex的动作

  def create(conn, %{"user" => user_params}) do
    user_changeset = User.changeset(%User{}, user_params)
    if user_changeset.valid? do
      Repo.transaction fn ->
        user = Repo.insert!(user_changeset)
        user_site = Ecto.Model.build(user, :user_sites, site: site_id(conn))
        Repo.insert!(user_site)

        conn
          |> put_flash(:info, "Your account was created")
          |> put_session(:current_user, user)
          |> redirect(to: "/")
      end
    else
      conn
        |> render("new.html", changeset: user_changeset)
    end
  end

【问题讨论】:

    标签: phoenix-framework elixir


    【解决方案1】:

    由于Plug.Conn是一个结构体,如果需要更改主机可以使用map update syntax

    conn = %{conn | host: "abc.com"}
    

    如果您想更改管道中的密钥,请使用Map.put/3

    conn =
      conn()
      |> put_header("content-type", "json")
      |> Map.put(:host, "abc.com")
    

    如果你想在每次测试之前运行一些东西,你可以使用ExUnit.Callbacks.setup/2

    setup do
      conn = %{conn() | host: "abc.com"}
      {:ok, conn}
    end
    
    test "foo", %{conn: conn} do
      get(conn, ...)
    end
    

    编辑

    如果你看https://github.com/elixir-lang/plug/blob/3835473fcf3a554a616d1bbcd2639aa63893be2c/lib/plug/adapters/test/conn.ex#L7

    您将看到主机由以下因素确定:

    host: uri.host || "www.example.com"
    

    在凤凰城由https://github.com/phoenixframework/phoenix/blob/b9ebbc2b9241b59dcac5d9c6d66fa248efe68a9c/lib/phoenix/test/conn_test.ex#L200调用

    这意味着为了得到你想要的主机,你需要在url中指定它,而不是conn

    试试这个:

    struct_url = %{MyApp.Endpoint.struct_url | host: "abc.com"}
    conn = get(conn, foo_url(struct_url, :index)) #note foo_url not foo_path
    

    edit2

    这里的问题是:

     Repo.transaction fn ->
        user = Repo.insert!(user_changeset)
        user_site = Ecto.Model.build(user, :user_sites, site: site_id(conn))
        Repo.insert!(user_site)
    
        conn
          |> put_flash(:info, "Your account was created")
          |> put_session(:current_user, user)
          |> redirect(to: "/")
      end
    

    错误告诉您action/2 应该返回Plug.Conn,但是您返回的是transaction/3 的结果(将是{:ok, value}{error, value}

    这意味着你的函数将返回{:ok, conn}

    试试:

     Repo.transaction fn ->
        user = Repo.insert!(user_changeset)
        user_site = Ecto.Model.build(user, :user_sites, site: site_id(conn))
        Repo.insert!(user_site)
      end
    
      conn
      |> put_flash(:info, "Your account was created")
      |> put_session(:current_user, user)
      |> redirect(to: "/")
    

    如果您想根据事务返回不同的结果,那么我会将整个部分移至 AccountService.create 之类的模块函数,然后使用以下 case 语句:

    def create(conn, %{"user" => user_params}) do
      case MessageService.create(user_params) do
        {:ok, user} ->
          |> put_flash(:info, "Your account was created")
          |> put_session(:current_user, user)
          |> redirect(to: "/")
        {:error, changeset} ->
          conn
          |> render("new.html", changeset: user_changeset)
      end
    end
    

    【讨论】:

    • 感谢 conn 确实在设置中得到了更新,但是当它进行测试时,它的主机值被重置。你知道为什么会这样吗?
    • 感谢@Gazler,我尝试了它并将我的发现更新为问题中的“编辑 2”。我仍然收到错误:(
    • @PratikKhadloya 请为您的控制器发布您的创建操作。
    • @PratikKhadloya 我很确定我找到了问题所在。查看我的最新编辑。
    • 太棒了!这有效@Gazler!感谢您的所有帮助。
    【解决方案2】:

    @chrismccord 帮我找到了答案。需要放在url中。

    conn = get conn(), "http://example.com/"

    【讨论】:

    • 你是想给某人发消息吗?看不到参考资料。
    • No :) 我只是想提及创建 Phoenix 框架的 Chris。
    【解决方案3】:

    我也遇到了这个问题。虽然似乎没有一种真正惯用的方法来解决这个问题,但我认为我解决它的方式相当干净。注意:这假设您只使用子域主机。

    首先,使用范围 => 主机映射更新您的配置,以便您的代码保持 DRY:

    # config.exs
    
    config :my_app, :scope_hosts,
      public: "www.",
      member_dashboard: "dashboard.",
      admin: "admin.",
      api: "api."
    

    创建RouterHelpers 模块并将其添加到您的router.ex

    # web/router.ex
    
    import MyApp.RouterHelpers
    
    scope host: get_scope_host(:admin), alias: MyApp, as: :admin do
      pipe_through [:browser, :admin_layout]
    
      get "/", Admin.PageController, :index
    end
    
    scope host: get_scope_host(:member_dashboard), alias: MyApp, 
      as: :member_dashboard do
    
      pipe_through [:browser, :member_dashboard_layout]
    
      get "/", MemberDashboard.PageController, :index
    end
    
    # web/helpers/router_helpers.ex
    
    defmodule MyApp.RouterHelpers do
      def get_scope_host(scope) when is_atom(scope) do 
        Application.get_env(:my_app, :scope_hosts)[scope]
      end
    end
    

    向您的 TestHelpers 模块添加一个测试助手,它将生成具有正确范围的完整 URL:

    # test/support/test_helpers.ex
    
    def host_scoped_path(path, scope) do
      host = MyApp.RouterHelpers.get_scope_host(scope)
      "http://#{host}myapp.test#{path}"
    end
    

    最后,在你的控制器测试中使用这个测试助手:

    # test/controllers/member_dashboard/page_controller_test.exs
    
    test "shows member dashboard index page", %{conn: conn} do
      conn = get conn, action_url(conn, :index)
      assert html_response(conn, 200)=~ "member_dashboard"
    end
    
    defp action_url(conn, action) do 
      member_dashboard_page_path(conn, action) 
      |> host_scoped_path(:member_dashboard)
    end
    

    【讨论】:

      猜你喜欢
      • 2019-02-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-22
      • 2016-12-24
      相关资源
      最近更新 更多