Unit testing controllers in Phoenix

I’m trying to test the index action of my controller, but I’m getting this error while doing it:

  ** (ArgumentError) maps cannot be converted to_param. A struct was expected, got: %{club_id: 0}

This is my controller:

defmodule DummyApiWeb.ItemController do
  use DummyApiWeb, :controller

  alias DummyApi.Items

  def index(conn, %{"club_id" => club_id }) do
    items = Items.list_items(club_id)
    render(conn, "index.json", items: items)
  end
...
end

And this is my test:

    test "lists all items", %{conn: conn} do
      conn = get(conn, Routes.club_item_path(conn, :index, %{"club_id" => 0 }))
      assert json_response(conn, 200)["data"] == []
    end

Can you please include the stacktrace from the error message?

 stacktrace:
       (phoenix) lib/phoenix/param.ex:77: Phoenix.Param.Map.to_param/1
       (dummy_api) DummyApiWeb.Router.Helpers.club_item_path/4
       test/dummy_api_web/controllers/item_controller_test.exs:32: (test)

Seems like your route helper expects a param for the url (:whatever in the router) in the place you‘re passing the GET query.

As LostKobrakai suggests - you probably want Routes.club_item_path(conn, :index, 0). Check out the examples:

https://hexdocs.pm/phoenix/routing.html#more-on-path-helpers

But why is it that when I try to use the update action like this:

 conn = put(conn, Routes.club_item_path(conn, :update, item.club_id, item.id,  @update_attrs))

I’m getting this ?:

 ** (ArgumentError) argument error
     code: conn = put(conn, Routes.club_item_path(conn, :update, item.club_id, item.id,  @update_attrs))

This is my update action:

def update(conn, %{"club_id" => club_id, "id" => id,  "item" => item_params}) do
 ...
end

You’re not using the functions on your route helpers correctly. It doesn’t matter how you implemented your controller actions.

E.g. the @update_attrs are quite likely supposed to be the third argument for put nad not the last argument for your path helper.

If I try:

conn = put(conn, Routes.club_item_path(conn, :update, item.club_id, item.id),  @update_attrs)

or

conn = put(conn, Routes.club_item_path(conn, :update, item.club_id, item.id), item: @update_attrs)

I still get ArgumentError. Which is weird because this post works:

conn = post(conn, Routes.club_item_path(conn, :create, club.id), item: @create_attrs )

The router helper takes a route in your controller like:

post “/api/clubs/:club_id/items”, ItemController, :create

(or a resource, which generates the above)

and provides a function that takes the conn or endpoint, the action (:create), and the :club_id, and returns the correct URL.

It doesn’t make sense to pass an item_id or item attributes to that function, they’re not part of the URL parameters.

If you have a different route like:

put “api/clubs/:club_id/items/:item_id”, ItemController, :update

then you’d pass both the :club_id and :item_id, because the URL has these two parameters.

Try calling h DummyAPIWeb.Router.Helpers.club_item_path in iex when your app is running, it’ll show you what arguments are expected.

Edit: for the second question, can you please include the full error? ArgumentError on its own is really vague.

iex(1)> h DummyApiWeb.Router.Helpers.club_item_path

  def club_item_path(conn_or_endpoint, action, club_id)


  def club_item_path(conn_or_endpoint, action, club_id, params)


  def club_item_path(conn_or_endpoint, action, club_id, id, params)

The full error is:

 ** (ArgumentError) argument error
     code: conn = delete(conn, Routes.club_item_path(conn, :delete, item.club_id))
     stacktrace:
       :erlang.apply({:ok, [item: %DummyApi.Items.Item{__meta__: #Ecto.Schema.Metadata<:loaded, "items">, author: "some author", club: #Ecto.Association.NotLoaded<association :club is not loaded>, club_id: 1, comments: #Ecto.Association.NotLoaded<association :comments is not loaded>, id: 1, inserted_at: ~N[2019-05-19 21:36:28],xbn: "some xbn", name: "some name", scheduled_meet_ups: #Ecto.Association.NotLoaded<association :scheduled_meet_ups is not loaded>, updated_at: ~N[2019-05-19 21:36:28], votes: #Ecto.Association.NotLoaded<association :votes is not loaded>}]}, :club_id, [])
       test/dummy_api_web/controllers/item_controller_test.exs:97: (test)

Are you sure the item variable really contains an item, and not a tuple like {:ok, item: [...]}?

2 Likes

:man_facepalming: That was it. My autogenerated fixture function was returning data in that format

 def fixture(:item) do
    {:ok, club}  =  Clubs.create_club(%{description: "some description", name: "some name"})
    {:ok, item} = Items.create_item(club, @create_attrs)
  end

Thanks a bunch for the help.