"shows chosen resource" test failing after using Money

I was using a float for prices but switched to Money, and although the show pages (and all other CRUD pages) are working, the mix-generated “shows chosen resource” test is now failing.

defmodule AppName.Admin.Product.MaterialControllerTest do
  use AppName.ConnCase

  alias AppName.Product.Material

  @valid_attrs %{name: "340gsm Mesh PVC", price_sq_m: "120.5"}
  @invalid_attrs %{}

  ...

  test "shows chosen resource", %{conn: conn} do
    material = Repo.insert! %Material{}
    conn = get conn, admin_material_path(conn, :show, material)
    assert html_response(conn, 200) =~ "Show material"
  end
  1) test shows chosen resource (AppName.Admin.Product.MaterialControllerTest)
     test/controllers/admin/product/material_controller_test.exs:30
     ** (FunctionClauseError) no function clause matching in Money.to_string/2
     stacktrace:
       lib/money.ex:394: Money.to_string(nil, [symbol: true])
       (app_name) web/templates/admin/product/material/show.html.eex:12: AppName.Admin.Product.MaterialView."show.html"/1
       (app_name) web/templates/layout/app.html.eex:29: AppName.LayoutView."app.html"/1
       (phoenix) lib/phoenix/view.ex:335: Phoenix.View.render_to_iodata/3
       (phoenix) lib/phoenix/controller.ex:642: Phoenix.Controller.do_render/4
       (app_name) web/controllers/admin/product/material_controller.ex:1: AppName.Admin.Product.MaterialController.action/2
       (app_name) web/controllers/admin/product/material_controller.ex:1: AppName.Admin.Product.MaterialController.phoenix_controller_pipeline/2
       (app_name) lib/app_name/endpoint.ex:1: AppName.Endpoint.instrument/4
       (app_name) lib/phoenix/router.ex:261: AppName.Router.dispatch/2
       (app_name) web/router.ex:1: AppName.Router.do_call/2
       (app_name) lib/app_name/endpoint.ex:1: AppName.Endpoint.phoenix_pipeline/1
       (app_name) lib/app_name/endpoint.ex:1: AppName.Endpoint.call/2
       (phoenix) lib/phoenix/test/conn_test.ex:224: Phoenix.ConnTest.dispatch/5
       test/controllers/admin/product/material_controller_test.exs:32: (test)

web/templates/admin/product/material/show.html.eex:

<h2>Show material</h2>

<ul>

  <li>
    <strong>Name:</strong>
    <%= @material.name %>
  </li>

  <li>
    <strong>Price per m²:</strong>
    <%= Money.to_string(@material.price_sq_m, symbol: true) %> <!-- line 12 -->
  </li>

</ul>

<%= link "Edit", to: admin_material_path(@conn, :edit, @material) %>
<%= link "Back", to: admin_material_path(@conn, :index) %>

I (probably mistakenly) thought this may be because I need to update the @valid_attrs :price_sq_m value from "120.5" to a new Money, but when I update to the following:

  @valid_attrs %{name: "340gsm Mesh PVC", price_sq_m: Money.new(120.5)}

I get:

$ mix test
** (FunctionClauseError) no function clause matching in Money.new/2
    lib/money.ex:72: Money.new(120.5, :GBP)
    test/controllers/admin/product/material_controller_test.exs:6: (module)
    (stdlib) erl_eval.erl:670: :erl_eval.do_apply/6
    (elixir) lib/code.ex:363: Code.require_file/2
    (elixir) lib/kernel/parallel_require.ex:57: anonymous fn/2 in Kernel.ParallelRequire.spawn_requires/5

18:23:31.449 [error] GenServer #PID<0.176.0> terminating
** (FunctionClauseError) no function clause matching in Money.new/2
    lib/money.ex:72: Money.new(120.5, :GBP)
    test/controllers/admin/product/material_controller_test.exs:6: (module)
    (elixir) src/elixir_compiler.erl:125: :elixir_compiler.dispatch_loaded/6
    (elixir) src/elixir_module.erl:192: :elixir_module.eval_form/6
    (elixir) src/elixir_module.erl:82: :elixir_module.do_compile/5
    (stdlib) erl_eval.erl:670: :erl_eval.do_apply/6
    (elixir) src/elixir.erl:223: :elixir.erl_eval/3
    (elixir) src/elixir.erl:211: :elixir.eval_forms/4
    (elixir) src/elixir_compiler.erl:66: :elixir_compiler.eval_compilation/3
    (elixir) src/elixir_lexical.erl:17: :elixir_lexical.run/3
    (elixir) src/elixir_compiler.erl:30: :elixir_compiler.quoted/3
    (elixir) lib/code.ex:363: Code.require_file/2
    (elixir) lib/kernel/parallel_require.ex:57: anonymous fn/2 in Kernel.ParallelRequire.spawn_requires/5

This test is the one remaining thing in my little CRUD and read-only test project that I’d like to fix before starting a course/book on Elixir, so if anyone has any pointers that would be grand. It is only a test, though, so this isn’t holding me up. Thanks!

According to stacktrace @material.price_sq_m is nil. So you probably are not saving it for some reason. Try maybe here

insert %Material{} with attributes and not an empty one :slight_smile: Those small errors happen to me all the time :wink:

And here you get this I think because you have to use integers, and can’t use floats. https://github.com/liuggio/money/blob/master/lib/money.ex#L72 that is why you get this error [quote=“BrightEyesDavid, post:1, topic:2607”]
** (FunctionClauseError) no function clause matching in Money.new/2
lib/money.ex:72: Money.new(120.5, :GBP)
[/quote]
Becaues there is no Money.new(x, y) when x is a float :slight_smile:

It’s because Money operates on the lowest nominal, ie not Dolars but Cents, not Pounds, but err… I don’t know what they use in UK :P, not Euros, but Eurocents.

Please do read again readme because it’s not so obvious at first glance. Look ie. at this example

iex> amount = Money.new(1_234_50)
%Money{amount: 123450, currency: :EUR}
iex> Money.to_string(amount, symbol: true, symbol_on_right: true, symbol_space: true)
"1.234,50 €"

123450 is not 123450 Eruos, but 1234 Euros and 50 Eurocents. :slight_smile:

Thanks very much for the response!

I now have the following for the test, which passes:

  test "shows chosen resource", %{conn: conn} do
    material = Repo.insert! %Material{name: "340gsm Mesh PVC", price_sq_m: Money.new(12050)}
    conn = get conn, admin_material_path(conn, :show, material)
    assert html_response(conn, 200) =~ "Show material"
  end

When generating a model and controller with, for example mix phoenix.gen.html Car cars name:string price:float, no parameters were inserted in the show test, and it passes without them. Only after changing to using Money do they seem to become necessary to stop it failing. Could I ask why that is please? (I’m planning to stop asking annoying questions about Phoenix and learn some basic Elixir soon!)

Also, it doesn’t seem like material = Repo.insert! %Material{@valid_attrs} can be used here. I expect that’s a syntax thing that’s easily dealt with if you actually, like, know some Elixir? :slight_smile:

Thanks again.

Those questions are not annoying, they are normal questions :slight_smile: The test fails because i assume previously you had in your view

<%= @material.price_sq_m %>

And it simply in template show what you inserted to the database. It showed nothing, nada, nil :slight_smile:
But now you have

<%= Money.to_string(@material.price_sq_m, symbol: true) %>

So that nil from database is passed into the function Money.to_string() as first argument. But function Money.to_string takes only Money struct as first argument, and didn’t know how to handle that nil from database. So it raised error:

     ** (FunctionClauseError) no function clause matching in Money.to_string/2
     stacktrace:
       lib/money.ex:394: Money.to_string(nil, [symbol: true])

saying loosely translated I can't match those parameters: (nil, [symbol: true]) to any version of of function Money.to_string that is arity of 2 (takes two arguments).

As you can see here https://github.com/liuggio/money/blob/master/lib/money.ex#L394
There is only one function Money.to_string/2 defined (there could be more, but that is another topic) and it takes only %Money{} struct as first argument.

In @valid_attrs %{name: "340gsm Mesh PVC", price_sq_m: "120.5"} price_sq_m is equal to string "120.5" if you would change that so it would be equal to Money.new(12050) it would probably pass.

Aha! I think I understand now. :blush: So the test’s material = Repo.insert! %Material{} was enough for the current “shows chosen resource” test even though it didn’t insert anything, because nothing in the view tripped over empty values.

Am I right in thinking a “shows chosen resource” test should always insert some actual data, and that the auto-generated tests are only an incomplete starting point? I was going to say that I was surprised that the auto-generated test didn’t insert dummy data, but at the point of using mix phoenix.generate it’s not specified that any fields are required if I understand correctly.

I did try that:

@valid_attrs %{name: "340gsm Mesh PVC", price_sq_m: Money.new(12050)}

material = Repo.insert! %Material{@valid_attrs}

but:

test/controllers/admin/product/material_controller_test.exs:30: expected key-value pairs in a map, got: @valid_attrs.

@valid_attrs does contain (is bound to?) a map as far as I can see. Perhaps module attributes can’t be used in the context of the Repo.insert call? I don’t yet understand the signature/types stated there.

Anyway, no matter. Thanks for your patience and help!

Autogenerated tests are there exactly there to be a starting point.

I think that you can’t create struct that way http://elixir-lang.org/getting-started/structs.html I don’t have a working app with Ecto right now to experiment, and too litle knowledege to just be sure. But I think that If you are using Phoenix and Ecto the best way is to insert into database a changeset Ecto.Changeset — Ecto v3.11.1 like this

@valid_attrs %{name: "340gsm Mesh PVC", price_sq_m: Money.new(12050)}

changeset = Material.changeset(%Material{}, @valid_attrs )
Repo.insert!(changeset)

That’s for tests, and in the app itself like this (because you wan’t to handle error instead of showing customer a HTTP 500 page)

@valid_attrs %{name: "340gsm Mesh PVC", price_sq_m: Money.new(12050)}

changeset = Material.changeset(%Material{}, @valid_attrs )
case Repo.insert(changeset) do
  {:ok, struct}       -> # Inserted with success where struct is a valid %Material{} inserted already into the database
  {:error, changeset} -> # Something went wrong where changeset has error field that you can, print, send them somwhere or whatever
    changeset.errors     # => [name: {"can't be blank", []}, price_sq_m: {"has an error or whatever", []}] 
end

But don’t take it for granted, I myself love Elixir but don’t have any time to write anything meaningful in it. And my knowledge is strictly theoretical :confused: