0
votes

I have a controller:

defmodule ParrotApi.MeetupController do
  use ParrotApi.Web, :controller
  alias ParrotApi.Meetup

  def index(conn, _params) do
    one_hour_ago = Timex.now
                   |> Timex.shift(hours: -1)
                   |> Timex.to_unix
    meetups = from(m in Meetup, where: m.timestamp >= ^one_hour_ago)
              |> Repo.all
              |> Repo.preload(:topic)
    render conn, meetups: meetups
  end
end

and a view for this controller:

defmodule ParrotApi.MeetupView do
  use ParrotApi.Web, :view

  def render("index.json", %{ meetups: meetups }) do
    render_many(meetups, ParrotApi.MeetupView, "meetup.json")
  end

  def render("meetup.json", %{ meetup: meetup }) do
    %{
      id: meetup.id,
      timestamp: meetup.timestamp,
      topic: meetup.topic.title,
    }
  end
end

I'm writing my first controller test:

defmodule ParrotApi.MeetupControllerTest do
  use ParrotApi.ConnCase
  alias ParrotApi.Meetup
  alias ParrotApi.Topic
  alias ParrotApi.Repo

  require Logger

  describe "#index" do
    test "returns future meetups" do
      topic_title = "Is Jesus your savior?"
      future_time = Timex.now
                    |> Timex.shift(minutes: 1)
                    |> Timex.to_unix
      {:ok, topic} = Topic.changeset(%Topic{}, %{title: topic_title}) |> Repo.insert
      {:ok, meetup} = Meetup.changeset(%Meetup{}, %{timestamp: future_time, topic_id: topic.id}) |> Repo.insert

      conn = build_conn()
      conn = get conn, meetup_path(conn, :index)

      assert json_response(conn, 200) == [
        %{
          "id" => meetup.id,
          "timestamp" => future_time,
          "topic" => topic_title,
        }
      ]
    end

It passes. However... it feels like a view test to me? It's testing the decorated/presented response that the view manipulates. So I don't really know what to do for my view test.

Is there a way to test the render conn, meetups: meetups response only, and then extract the stuff currently in my controller test into a view test? Or is this the wrong way to test things?

1
This may be rather subjective, but when I do controller tests, I mostly just make sure that I am getting the response I expect. e.g., 200 status code when I expect the page to load, 302 for a redirect when a user is not logged in, etc. Then in my view tests, I ensure that certain content is loaded and displayed.Justin Wood
Would you care to show an example?bigpotato

1 Answers

1
votes

Although I think testing best practices are a lot about a developers opinions and biases, I strongly agree that your test is testing something outside of its responsibility and that the actual JSON structure should be tested in the view test.

Personally, I prefer testing json responses for their content as well as the response status in the controller test, however your test makes a very strong assumption about the "how is the jsons structured" instead of "what is the content".

You would usually leave the json creation to the json creator (your view) and only care about "does the controller render what it is supposed to render?"

If I was pressed to sum up the most important thing for testing in one sentence it would be: "Formulate what you want to test in your mind and then try to make your testing code read exactly like that". So basically what I want my test to read like is this:

I am expecting a json encoded response with the status code 200 of all future meetups.

test "returns future meetups" do
  topic_title = "Is Jesus your savior?"
  future_time = Timex.now
                |> Timex.shift(minutes: 1)
                |> Timex.to_unix
  {:ok, topic} = Topic.changeset(%Topic{}, %{title: topic_title}) |> Repo.insert
  {:ok, meetup} = Meetup.changeset(%Meetup{}, %{timestamp: future_time, topic_id: topic.id}) |> Repo.insert

  conn = build_conn()
  conn = get conn, meetup_path(conn, :index)

  future_meetups_json = MeetupView.render("index.json", %{meetups: MeetupQuery.future_meetups()})

  assert conn.status == 200
  assert conn.response_body == future_meetups_json
end

With the meetup query tucked away in a seperate method, you can test it seperately to ensure it really only returns the meetups it should return and the actual view testing can be done in the view tests :)