Calling a constructor with a database component from a Phoenix test

I have a module for Happymeal which takes consists of :food (String) and :toy (simple database object with :id as primary key)

The constructor looks like this:

defmodule Myapp.Happymeal do

  alias Myapp.{Repo, Toy}

  defstruct [
    :toy,
    :food
  ]

  @type t :: %__MODULE__{
          toy: Toy,
          food: String
        }

  def new(%{toy_id: toy_id, food: food}) do
    toy = Repo.get(Toy, toy_id)

    {:ok,
     %__MODULE__{
       toy: toy,
       food: food
     }}
  end
end

When I run Myapp.Happymeal.new(%{toy_id: 1, food: "burger"}) in iex I get back a Happymeal as expected.

However, when I run this from a test:

defmodule MyappTest do
  use ExUnit.Case 

  describe "Happymeal tests" do
    test "Happymeal can be created" do
      {:ok, happymeal} = Myapp.Happymeal.new(%{toy_id: 1, food: "burger"})

      IO.inspect(happymeal)
    end
  end
end

… I get an error:

** (DBConnection.OwnershipError) cannot find ownership process for #PID<0.341.0>.

 When using ownership, you must manage connections in one
 of the four ways:

 * By explicitly checking out a connection
 * By explicitly allowing a spawned process
 * By running the pool in shared mode
 * By using :caller option with allowed process

 The first two options require every new process to explicitly
 check a connection out or be allowed by calling checkout or
 allow respectively.

 The third option requires a {:shared, pid} mode to be set.
 If using shared mode in tests, make sure your tests are not
 async.

 The fourth option requires [caller: pid] to be used when
 checking out a connection from the pool. The caller process
 should already be allowed on a connection.

 If you are reading this error, it means you have not done one
 of the steps above or that the owner process has crashed.

 See Ecto.Adapters.SQL.Sandbox docs for more information.
 code: {:ok, happymeal} = Myapp.Happymeal.new(%{toy_id: 1, food: "burger"})

How can I make this style of constructor work from within a phoenix test?

Hi,
instead of using
use ExUnit.Case
use
use Myapp.DataCase

Some further detail, just for completeness and to show code that to my mind is a little more idiomatic.

I created a similar project using…

mix phx.new myapp
cd myapp/
mix ecto.create
mix phx.gen.schema Toy toys name:string
mix phx.gen.schema HappyMeal happy_meals food:string toy_id:references:toys
mix ecto.migrate

Then made minor changes to happy_meal.ex so it looks like…

defmodule Myapp.HappyMeal do
  use Ecto.Schema
  import Ecto.Changeset
  alias Myapp.Toy

  schema "happy_meals" do
    field(:food, :string)
    belongs_to(:toy, Toy)

    timestamps()
  end

  @doc false
  def changeset(happy_meal, toy_id, attrs) do
    happy_meal
    |> cast(attrs, [:food])
    |> change(toy_id: toy_id)
    |> validate_required([:food])
  end
end

And added the test my_app_test.exs

defmodule MyappTest do
  use Myapp.DataCase

  alias Myapp.{Toy, HappyMeal}

  test "Happymeal can be created" do
    {:ok, toy_one} = Repo.insert(%Toy{name: "Buzz Lightyear"})

    %HappyMeal{}
    |> HappyMeal.changeset(toy_one.id, %{food: "burger"})
    |> Repo.insert()
    |> IO.inspect()
  end
end

2 Likes

I was trying to create a constructor that existed outside of the Ecto Changeset/Schema paradigm…what I’m doing is almost certainly a bad practice, but I’m curious as to why it doesn’t work.

1 Like

If you’re just doing a bare use ExUnit.Case and not one of the helpers that Phoenix generates for you, then you need to check out a Database connection for the test.

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

Notably, this is unrelated to the use of a “constructor”, this is necessary to do any database calls at all within the test. By checking out the connection it allows Ecto to ensure all changes done within the test are isolated from every other test.

4 Likes