URL Router Helpers not working in tests

Having a View, with a function using the Routes.*_url helper, everything works fine, until I try to test this function directly.

defmodule ExampleWeb.PageView do
  use ExampleWeb, :view

  def url_helper(conn, slug) do
    Routes.page_url(conn, :show, slug)
  end
end
defmodule ExampleWeb.PageViewTest do
  use ExampleWeb.ConnCase, async: true

  alias ExampleWeb.PageView

  test "url_helper/2", %{conn: conn} do
    slug = "my-example"

    assert PageView.url_helper(conn, slug) == "http://localhost:4002/my-example"
  end
end

Result when running the test:

1) test url_helper/2 (ExampleWeb.PageViewTest)
   test/example_web/views/page_view_test.exs:12
   ** (CaseClauseError) no case clause matching: %{phoenix_recycled: true, plug_skip_csrf_protection: true}
   code: assert PageView.url_helper(conn, slug) == "http://localhost:4000/my-example"
   stacktrace:
     (phoenix 1.5.3) lib/phoenix/router/helpers.ex:18: Phoenix.Router.Helpers.url/2
     (example 0.1.0) lib/example_web/router.ex:1: ExampleWeb.Router.Helpers.page_url/4
     test/example_web/views/page_view_test.exs:15: (test)

If I change this to use the Routes.*_path helper everything works as intended, but I need the complete and absolute urls form time to time.

Did I miss anything when setting up the conn for testing?

1 Like

What is the endpoints configuration for :test, especially the :url field?

I’m pretty sure those are just the ones generated by mix phx.new, nothing fancy or custom yet.

in config.exs:

config :example, ExampleWeb.Endpoint,
  url: [host: "localhost"],
  secret_key_base: "A VERY RANDOM STRING",
  render_errors: [view: ExampleWeb.ErrorView, accepts: ~w(html json), layout: false],
  pubsub_server: Example.PubSub,
  live_view: [signing_salt: "RANDOM"]

in test.exs:

config :example, ExampleWeb.Endpoint,
  http: [port: 4002],
  server: false

I’m at home now and therefore have a bit more time to actually take a look :smiley:

The conn in oyur tests is a connectiont that is created from thin air and has no associated endpoint. Though the endpoint is required for the URL helpers to actually create the URL.

You can use Phoenix.ConnTest.bypass_through/x (IIRC) to make that stub connection a “full” one with the sideeffect of it passing through the full pipeline stack, which might make the test require a significant amount more time.

2 Likes

Thanks, that’s exactly what I’m doing right now.

assert conn |> bypass_through() |> get("/") |> PageView.url_helper(slug) ==
             "http://localhost:4002/my-example"

I just hoped there was a way to test a View without actually calling a Route I didn’t knew about. This still feels a little wrong in my opinion.

To be honest, I don’t really understand, why you need to test the URL helpers. That is basically testing if the path helpers do their job while tightly coupling test results to certain config values…

1 Like

I just trimmed this down to have a somewhat minimal example here. In my real app I’m of course not only having the URL helper in there, I have a function that uses them to create an RSS feed. I wanted to generate this in the view, so I have a nice and clean controller like:

def index(conn, _params) do
  posts = Blog.list_posts()

  conn
  |> put_resp_header("content-type", "application/rss+xml")
  |> render("index.rss", posts: posts)
end

in my View I then have a little function that turns a Post into an Item for rendering the xml and I need the full urls for that.

That still doesn’t answer the question why you would basically unit-test Phoenix itself. :slightly_smiling_face:

The URL helpers are working, you can be very sure of it.

I don’t want to unit-test Phoenix or the url helper itself. I just want to test a function in one of my views that happens to use one of those helpers, because I need absolute urls in there.

The conn you get from MyApp.ConnTest is not linked to any particular endpoint. You could have multiples of those in your project. Therefore you need to link that conn (basically a plain request) to an endpoint before you can use it to unit test things depending on a conn for a specific endpoint. Without that linkage the url generation doesn’t know which host to use, which path was requested, if the endpoint was mounted to a subfolder, …

For most ConnTests those things are set when you do get conn, path, …. but for unit tests you need to handle it on your own.

3 Likes

Hi LostKobrakai, when you say “handle it on your own”, do you mean basically setting the private :phoenix_endpoint in conn manually?

Looking at https://hexdocs.pm/phoenix/Phoenix.ConnTest.html#content it would seem like setting a @endpoint MyAppWeb.HomeController would suffice but it does not.

Edit: did answer the wrong thing before.

There is Phoenix.ConnTest.bypass_through, which is sometimes needed. Not sure if there is something more targeted to just the endpoint.