I am making a multisite application. I want to set the request host on the connection prior to testing the controller. In Rails we could do this using
before :each do
request.env["HTTP_REFERER"] = '/'
end
Can someone suggest how to do the same in Phoenix?
Edit 1: I could set the host using
conn |> put_req_header("host", "abc.com")
, but that did not change the host
property in the conn
object. It still points to "www.example.com"
Edit 2: I also tried
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
But i got the following error:
$ 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 line 1 is defmodule Myapp.RegistrationControllerTest do
Edit 3:
Create action of 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
Since Plug.Conn
is a struct, if you need to change the host you can use the map update syntax:
conn = %{conn | host: "abc.com"}
If you want to change a key in a pipeline then use Map.put/3:
conn =
conn()
|> put_header("content-type", "json")
|> Map.put(:host, "abc.com")
If you want something to run before each test you can use ExUnit.Callbacks.setup/2
setup do
conn = %{conn() | host: "abc.com"}
{:ok, conn}
end
test "foo", %{conn: conn} do
get(conn, ...)
end
Edit
If you look at https://github.com/elixir-lang/plug/blob/3835473fcf3a554a616d1bbcd2639aa63893be2c/lib/plug/adapters/test/conn.ex#L7
You will see that the host is determined by:
host: uri.host || "www.example.com"
Which is called in Phoenix by https://github.com/phoenixframework/phoenix/blob/b9ebbc2b9241b59dcac5d9c6d66fa248efe68a9c/lib/phoenix/test/conn_test.ex#L200
This means that in order to get the host that you want, you need to specify it in the url, not the conn
Try this:
struct_url = %{MyApp.Endpoint.struct_url | host: "abc.com"}
conn = get(conn, foo_url(struct_url, :index)) #note foo_url not foo_path
edit2
The problem here is:
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
The error is telling you that action/2
should return a Plug.Conn
however you are returning the result of the transaction/3 (which will be either {:ok, value}
or {error, value}
This means your function will be returning {:ok, conn}
Try:
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: "/")
If you want to return a different result based on the transaction then I would move the whole section to a module function like AccountService.create
and then use a case statement like:
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 helped me find the answer. Needed to put it in the url.
conn = get conn(), "http://example.com/"
I ran into this problem as well. Although there doesn't seem to be a truly idiomatic way to solve the problem, I think the way I solved it is fairly clean. Note: this assumes you're only using subdomain hosts.
Firstly, update your config with a scope => host map so that your code stays DRY:
# config.exs
config :my_app, :scope_hosts,
public: "www.",
member_dashboard: "dashboard.",
admin: "admin.",
api: "api."
Create and add a RouterHelpers
module to your 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
Add a test helper to your TestHelpers
module that will generate a full URL with the correct scope:
# 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
Finally, make use of this test helper in your controller tests:
# 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With