I am having inconsistent behavior with ExMachina. Within a Factory, I can get one nested insert to work but not another. But I know that both factories work if used outside this factory. I keep getting this esoteric error message:
** (CaseClauseError) no case clause matching: 3
As an aside … this is a very confusing error message for beginners. The number at the end changes with each run. Using IO.puts, I figured out that it has to do with a relationship between my Author and my Resource. It is a many-many relationship. The Author inserted within my Resource Factory fails to produce an ID so that relationship is incomplete which causes the test to fail.
The Author Factory works: If I create an Author using insert(:author) in the test, the author is created perfectly with an ID. So I know that the Author Factory is capable of producing an author that is inserted into the database.
But when I create an Author inside the Resource Factory, it fails to generate an ID. When I create a User inside the Resource Factory, it produces a User with an ID. This is what is so frustrating. The User Factory works within the Resource Factory but the Author Factory does not!
Below are the Author Factory and Resource Factory. Below that is the IO.puts output from within the Resource Factory. Those printouts show me that build(:user) produces a User with an ID that is used by Resource. But build(:author) does not produce an Author with an ID so that relationship within Resource is never completed.
This makes no sense. I have verified that the Author Factory and User Factory work. So why does one work inside the Resource Factory but the other one does not???
STANDARD FACTORY SETUP
defmodule MyApp.Factory do
use ExMachina.Ecto, repo: MyApp.Repo
use MyApp.UserFactory
use MyApp.AuthorFactory
use MyApp.ResourceFactory
end
AUTHOR FACTORY
defmodule MyApp.AuthorFactory do
alias MyApp.Authors.Author
defmacro __using__(_opts) do
quote do
# BEGIN Author Factory
def author_factory(attrs \\ %{}) do
user = build(:user)
author =
%Author{
first_name: sequence(:first_name, &"Author#{&1}fn"),
last_name: sequence(:last_name, &"Author#{&1}ln"),
company_name: sequence(:company_name, &"Author#{&1}Company"),
website_url: sequence(:website_url, &"https://author#{&1}.com"),
city: "",
state_region: "",
country: "",
user: user,
user_id: user.id
}
merge_attributes(author, attrs)
end
# END Author Factory
end
end
end
RESOURCE FACTORY
defmodule MyApp.ResourceFactory do
alias MyApp.Resources.Resource
defmacro __using__(_opts) do
quote do
# BEGIN Resource Factory
def resource_factory(attrs \\ %{}) do
IO.puts "--------- Resource Factory -------------"
user = build(:user)
IO.puts "User:"
user |> IO.inspect(charlists: :as_lists, limit: :infinity)
author = build(:author)
IO.puts "Author:"
author |> IO.inspect(charlists: :as_lists, limit: :infinity)
resource =
%Resource{
title: sequence("Resource"),
description: sequence("This is Resource"),
image_url: sequence(:image_url, &"https://www.resourceimage#{&1}.com"),
resource_url: sequence(:resource_url, &"https://www.resource#{&1}.com"),
publish_date: nil,
is_free: false,
is_private: false,
usage_count: 0,
user_id: user.id,
authors: [author.id]
}
return = merge_attributes(resource, attrs)
IO.puts "Resource:"
return |> IO.inspect(charlists: :as_lists)
return
end
# END Resource Factory
end
end
end
OUTPUT FROM IO.PUTS
--------- Resource Factory -------------
User:
#MyApp.Users.User<
__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
avatar: nil,
bio: nil,
city: "Santa Cruz",
confirmed_at: nil,
country: "United States",
created_groups: #Ecto.Association.NotLoaded<association :created_groups is not loaded>,
email: "user-576460752303422911@example.com",
first_name: "User_fn1",
geo_id: 29,
geolocation: #Ecto.Association.NotLoaded<association :geolocation is not loaded>,
groups: #Ecto.Association.NotLoaded<association :groups is not loaded>,
hashed_password: “Not sure if I should send this around so just hiding this but it’s the usual hashed stuff”,
**id: 222,**
inserted_at: ~N[2023-07-04 15:25:18],
last_name: "User_ln1",
latlong: %Geo.Point{
coordinates: {-122.0308, 36.9741},
properties: %{},
srid: 4326
},
lessons: #Ecto.Association.NotLoaded<association :lessons is not loaded>,
state: "California",
timezone: "America/Los_Angeles",
updated_at: ~N[2023-07-04 15:25:18],
username: "user_username1",
zip: nil,
...
>
Author:
%MyApp.Authors.Author{
__meta__: #Ecto.Schema.Metadata<:built, "authors">,
city: "",
company_name: "Author1Company",
country: "",
first_name: "Author1fn",
**id: nil,**
inserted_at: nil,
last_name: "Author1ln",
resources: #Ecto.Association.NotLoaded<association :resources is not loaded>,
user: #MyApp.Users.User<
__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
avatar: nil,
bio: nil,
city: "Santa Cruz",
confirmed_at: nil,
country: "United States",
created_groups: #Ecto.Association.NotLoaded<association :created_groups is not loaded>,
email: "user-576460752303422879@example.com",
first_name: "User_fn2",
geo_id: 29,
geolocation: #Ecto.Association.NotLoaded<association :geolocation is not loaded>,
groups: #Ecto.Association.NotLoaded<association :groups is not loaded>,
hashed_password: “blah blah”,
id: 223,
inserted_at: ~N[2023-07-04 15:25:18],
last_name: "User_ln2",
latlong: %Geo.Point{
coordinates: {-122.0308, 36.9741},
properties: %{},
srid: 4326
},
lessons: #Ecto.Association.NotLoaded<association :lessons is not loaded>,
state: "California",
timezone: "America/Los_Angeles",
updated_at: ~N[2023-07-04 15:25:18],
username: "user_username2",
zip: nil,
...
>,
user_id: 223,
users: #Ecto.Association.NotLoaded<association :users is not loaded>,
state_region: "",
updated_at: nil,
website_url: "https://author1.com"
}
Resource:
%MyApp.Resources.Resource{
__meta__: #Ecto.Schema.Metadata<:built, "resources">,
**authors: [nil],**
description: "This is Resource0",
id: nil,
image_url: "https://www.resourceimage0.com",
inserted_at: nil,
is_free: false,
is_private: false,
mediums: [1],
publish_date: nil,
resource_types: [3],
resource_url: "https://www.resource0.com",
user: #Ecto.Association.NotLoaded<association :user is not loaded>,
**user_id: 222,**
users: #Ecto.Association.NotLoaded<association :users is not loaded>,
supplies: [1],
title: "Resource0",
topics: [23],
updated_at: nil,
usage_count: 0
}
The test is incredibly basic and is below:
defmodule MyAppWeb.ResourceLiveTest do
use MyAppWeb.ConnCase
import MyApp.Factory
import Phoenix.LiveViewTest
alias MyApp.Resources
describe "RESOURCE FILTERING - " do
IO.puts "-------------- TEST IO PUTS -------------------------"
setup do
setup_author = insert(:author)
IO.puts "SETUP AUTHOR"
setup_author |> IO.inspect(charlists: :as_lists)
resource0 = insert( :resource)
[resource0: resource0]
end
test "lists all resources", %{conn: conn, resource0: resource0} do
# {:ok, _index_live, html} = live(conn, Routes.resource_index_path(conn, :index))
IO.puts "Resource0:"
resource0 |> IO.inspect(charlists: :as_lists)
# assert html =~ "Listing Resources"
# assert html =~ resource.description
end
end # end describe
end # end module