Phoenix控制器测试,设置请求主机

时间:2015-11-28 20:31:21

标签: elixir phoenix-framework elixir-framework

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

执行此操作
before :each do
  request.env["HTTP_REFERER"] = '/'
end

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

编辑1 :我可以使用设置主机 conn |> put_req_header("host", "abc.com"),但这并未更改host对象中的conn属性。它仍然指向“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

3 个答案:

答案 0 :(得分:8)

由于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

的凤凰城中调用了哪个

这意味着为了获得您想要的主机,您需要在网址中指定它,而不是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

答案 1 :(得分:2)

@chrismccord帮助我找到答案。需要把它放在网址中。

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

答案 2 :(得分:0)

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

首先,使用scope =&gt;更新您的配置主机映射,以便您的代码保持干燥状态:

# 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