凤凰控制器测试,设置请求主机
Phoenix controller test, set request host
我正在制作一个多站点应用程序。我想在测试控制器之前在连接上设置请求主机。在 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
因为 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
编辑
您将看到主机由以下因素决定:
host: uri.host || "www.example.com"
在 Phoenix 中称为
这意味着为了获得您想要的主机,您需要在 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
应该 return 一个 Plug.Conn
但是你正在 returning transaction/3 的结果(这将是{:ok, value}
或 {error, value}
这意味着您的函数将 returning {: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: "/")
如果你想 return 基于事务的不同结果,那么我会将整个部分移动到模块函数,如 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
@chrismccord 帮我找到了答案。需要把它放在 url.
conn = get conn(), "http://example.com/"
我运行也遇到了这个问题。虽然似乎没有真正惯用的方法来解决问题,但我认为我解决它的方法相当干净。注意:这假设您只使用子域主机。
首先,使用范围 => 主机映射更新您的配置,以便您的代码保持 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
我正在制作一个多站点应用程序。我想在测试控制器之前在连接上设置请求主机。在 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
因为 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
编辑
您将看到主机由以下因素决定:
host: uri.host || "www.example.com"
在 Phoenix 中称为
这意味着为了获得您想要的主机,您需要在 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
应该 return 一个 Plug.Conn
但是你正在 returning transaction/3 的结果(这将是{:ok, value}
或 {error, value}
这意味着您的函数将 returning {: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: "/")
如果你想 return 基于事务的不同结果,那么我会将整个部分移动到模块函数,如 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
@chrismccord 帮我找到了答案。需要把它放在 url.
conn = get conn(), "http://example.com/"
我运行也遇到了这个问题。虽然似乎没有真正惯用的方法来解决问题,但我认为我解决它的方法相当干净。注意:这假设您只使用子域主机。
首先,使用范围 => 主机映射更新您的配置,以便您的代码保持 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