Notes to self

Testing Phoenix custom error pages

A short post on using assert_error_sent to test custom error responses on status code, response headers, and body. And when it doesn’t work.

When using Phoenix, we are provided with standard error responses, and so we can just simply test on raises like in the following 404 Not Found example:

assert_raise Ecto.NoResultsError, fn ->
  conn
  |> using_basic_auth(@username, @password)
  |> get(Routes.record_path(conn, :show, record))
end

But it’s better to be a bit more implicit in our intentions when testing a controller:

assert_error_sent 404, fn ->
  conn
  |> using_basic_auth(@username, @password)
  |> get(Routes.record_path(conn, :show, record))
end

assert_error_sent translates regular raises of the provided function to an array of HTML status, headers, and body if the resulting error is as expected. Note that we can also use :not_found instead of the 404 number.

What if we implement our custom 404 page? Maybe we are sending a specific error message from our API. Our custom response could look like the following:

defmodule MyWeb.ErrorView do
  use MyWeb, :view
  ..
  # 404 Not Found
  def render("404.json", _assigns) do
     %{errors: [%{status: "404", title: "Not Found"}]}
  end
  ..
end

At this moment it starts to make sense to test that we are sending the error in the right format to the clients. We can do so by matching on assert_error_sent as follows:

...
test "not found when ...", %{conn: conn, record: record} do
  response = assert_error_sent 404, fn ->
    conn
    |> using_basic_auth(@username, @password)
    |> get(Routes.record_path(conn, :show, record))
  end
  expected = Jason.encode!(%{"errors" => [%{"status" => "404", "title" => "Not Found"}]})
  assert {404, [{"content-type", "application/json"} | _t], ^expected} = response
end

By using assert and pattern matching we can easily test the resulting status code, any important headers, and the expected body. Note that we have to “pin” the expected variable for pattern matching with “^”. First time I implemented this I did not pin the variable and I lived in the false assumption that everything is working!

Also, assert_error_sent won’t work if you use debug_errors: true in your application endpoint configuration though (luckily it’s not a default for the test environment). Took me some time to find out what’s wrong.

Finally if you need to map your own exceptions to status codes look into Custom Errors documentation.

Check out my book
Interested in Ruby on Rails default testing stack? Take Minitest and fixtures for a spin with my latest book.

Get Test Driving Rails while it's in prerelease.

by Josef Strzibny
RSS