NatassiaTavares

NatassiaTavares

Service testing - Ecto.Cast error

I’m testing the product_service for my API and I’m getting this error:

** (FunctionClauseError) no function clause matching in Ecto.Changeset.cast/4

when attempting to create or update an object.

Here’s the code:
product_service_test.exs

defmodule Warehouse.ProductServiceTest do
  use ExUnit.Case, async: true
  import Ecto.Query, warn: false
  import Morphix
  alias Warehouse.ProductService
  alias Warehouse.Repo

  setup do
    :ok = Ecto.Adapters.SQL.Sandbox.checkout(Warehouse.Repo)
  end

  describe " ProductService.create_or_update_product " do
    test 'creates a product' do
      {:ok, atomized_product} = Morphix.atomorphiform(product())
      ProductService.create_or_update_product(atomized_product)
      assert Repo.one(from p in "products", select: count(p.id)) == 1
    end

    test 'updates a product' do
      {:ok, product_created} = ProductService.create_or_update_product(product())

      product_changed = Map.replace!(product(), :title, "new title")
      {:ok, product_updated} = ProductService.create_or_update_product(product_changed)
      
      assert product_updated.id == product_created.id
      assert product_updated.title == "new title"
    end
  end
end

product_service.ex

defmodule Warehouse.ProductService do
	alias Warehouse.ProductRepo
  alias Warehouse.Product

  def list_products(store_id) do
    ProductRepo.list_products(store_id)
  end

  def get_product!(id) do
    ProductRepo.get_product!(id)
  end

  def create_product(attrs \\ %{}) do
    ProductRepo.create_product(attrs)
  end

  def get_product_by_sku(sku, store_id) do
    ProductRepo.get_product_by_sku(sku, store_id)
  end

  def create_or_update_product(product_to_update \\ %{}) do
    product = get_product_by_external_code(product_to_update)
    if product != nil do
      valid_product_to_update = prepare_changeset(product_to_update)
      ProductRepo.update_product(product, valid_product_to_update)
    else
      ProductRepo.create_product(product_to_update)
    end
  end
  
  defp prepare_changeset(new_product) do
    new_product
    |> Map.delete(:sku)
    |> Map.delete(:gtin)
  end
end

product_repo.ex

defmodule Warehouse.ProductRepo do
  import Ecto.Query, warn: false
  alias Warehouse.Repo
  alias Warehouse.Product

  def list_products(store_id) do
    Repo.all from p in Product, preload: [:images, :videos, :listings], where: p.store_id == ^store_id
  end

  def get_product!(id) do 
    Repo.get!(Product, id)
    |> Repo.preload([:images, :videos, :provider])
  end

  def create_product(attrs \\ %{}) do
    %Product{}
    |> Product.changeset(attrs)
    |> Repo.insert()
  end

  def update_product(product, new_product) do    
    product
    |> Product.changeset(new_product)
    |> Repo.update()
  end
end

product.ex

defmodule Warehouse.Product do
  use Ecto.Schema
  import Ecto.Changeset
  import EctoEnum

  defenum ConditionEnum, :condition, [:new, :used, :refurbished]

  @serializables [:id, :store_id, :title, :condition, :price, :promotional_price, :available_quantity,
    :sku, :currency, :gtin, :short_description, :description, :height, 
    :lenght, :width, :weight, :condition, :provider_id]

  @derive { Jason.Encoder, only:  Enum.concat(@serializables, [:images, :listings]) }

  schema "products" do
    timestamps()

    belongs_to :provider, Warehouse.Provider
    
    has_many :images, Warehouse.Image, on_replace: :delete
    has_many :videos,  Warehouse.Video, on_replace: :delete
    has_many :listings, Warehouse.Listing, on_replace: :delete

    field :store_id, :integer
    field :title, :string
    field :price, :decimal
    field :promotional_price, :decimal
    field :available_quantity, :integer
    field :sku, :string
    field :currency, :string
    field :gtin, :string
    field :short_description, :string
    field :description, :string
    field :height, :integer
    field :lenght, :integer
    field :width, :integer
    field :weight, :integer
    field :condition, :string
  end

  @doc false
  def changeset(product, attrs \\ %{}) do
    product
    |> cast(attrs, @serializables)
    |> cast_assoc(:images)
    |> cast_assoc(:videos)
    |> cast_assoc(:listings)
    |> validate_required([:store_id])
  end
end

The full error message:

 1) test  ProductService.create_or_update_product  updates a product (Warehouse.ProductServiceTest)
     test/warehouse/product_service_test.exs:19
     ** (FunctionClauseError) no function clause matching in Ecto.Changeset.cast/4

     The following arguments were given to Ecto.Changeset.cast/4:

         # 1
         %{"available_quantity" => 2, "condition" => "new", "currency" => "BRL", "description" => "description", "gtin" => "s", "height" => 2, "images" => [%{"url" => "some_url"}], "lenght" => 2, "listings" => [%{"external_code" => "some_code", "sales_channel_id" => 1}], "price" => 2.3, "short_description" => "a", "sku" => "somesku", "store_id" => 3, "title" => "some title", "unity" => "KG", "videos" => [%{"url" => "some_url"}], "width" => 2}

         # 2
         %{"available_quantity" => 2, "condition" => "new", "currency" => "BRL", "description" => "description", "gtin" => "s", "height" => 2, "images" => [%{"url" => "some_url"}], "lenght" => 2, "listings" => [%{"external_code" => "some_code", "sales_channel_id" => 1}], "price" => 2.3, "short_description" => "a", "sku" => "somesku", "store_id" => 3, "title" => "some title", "unity" => "KG", "videos" => [%{"url" => "some_url"}], "width" => 2}

         # 3
         [:id, :store_id, :title, :condition, :price, :promotional_price, :available_quantity, :sku, :currency, :gtin, :short_description, :description, :height, :lenght, :width, :weight, :condition, :provider_id]

         # 4
         []

     Attempted function clauses (showing 5 out of 5):

         def cast(_data, %{__struct__: _} = params, _permitted, _opts)
         def cast({data, types}, params, permitted, opts) when is_map(data)
         def cast(%Ecto.Changeset{types: nil}, _params, _permitted, _opts)
         def cast(%Ecto.Changeset{changes: changes, data: data, types: types, empty_values: empty_values} = changeset, params, permitted, opts)
         def cast(%{__struct__: module} = data, params, permitted, opts)

     code: {:ok, product_created} = ProductService.create_or_update_product(product())
     stacktrace:
       (ecto) lib/ecto/changeset.ex:463: Ecto.Changeset.cast/4
       (warehouse) lib/warehouse/models/product.ex:43: Warehouse.Product.changeset/2
       (warehouse) lib/warehouse/repositories/product_repo.ex:24: Warehouse.ProductRepo.update_product/2
       test/warehouse/product_service_test.exs:20: (test)

Seems like Elixir is matching a Ecto.Changeset.cast/3 into a Ecto.Changeset.cast/4 adding an empty array as the 4th parameter. This is happening in every test with ecto, is working fine in the API, I have no idea what is causing this.

First Post!

idi527

idi527

:waving_hand:

Seems like Elixir is matching a Ecto.Changeset.cast/3 into a Ecto.Changeset.cast/4 adding an empty array as the 4th parameter. This is happening in every test with ecto, is working fine in the API, I have no idea what is causing this.

I think the problem is with the first argument, actually. It should probably be a product struct instead of plain map in your case.

What does get_product_by_external_code/1 return? Since ProductRepo.update_product(product, valid_product_to_update) since to be getting two maps according to the stacktrace?

Where Next?

Popular in Questions Top

vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
PeterCarter
There are pre-rolled solutions for other frameworks that do work. However, Phoenix does not seem to have these. Have people had good expe...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New

Other popular topics Top

KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36820 110
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
gausby
I asked this very same question on twitter and got some interesting feedback, but I thought it would be a good question to ask here as we...
1207 40165 209
New

We're in Beta

About us Mission Statement