Ecto Sandbox use checked out connection for existing process

1.5k Views Asked by At

I have a Phoenix Test Application with a Product schema. I have a GenServer started by the main application supervisor that gets a list of the products with handle_call.

def handle_call(:get_products, _from, _state)
  products = Repo.all(Product)
  {:reply, products, products}
end

Now I want to write a test for this GenServer.

I tried to do something like this in the test

setup do
  pid = Process.whereis(MyGenServer)
  Ecto.Adapters.SQL.Sandbox.allow(Repo, self(), pid)
  ProductFactory.insert_list(3, :product) # using ExMachina for factories
end

The 3 products get created, I can find them in the test with Repo.all(Product), however running the MyGenServer.get_products() will return an empty array.

I am not getting any error, but just returns an empty array, as if no products exist.

Is there any way to allow the existing PID to use the checkout sandbox connection, and retrieve my products in the GenServer process?

PS. I managed to run the test by restarting the GenServer process in the test setup, but I was wondering if there is a more "elegant" way to solve the issue.

setup do
  Supervisor.terminate_child(MyApp.Supervisor, MyGenServer)
  Supervisor.restart_child(MyApp.Supervisor, MyGenServer)
  ProductFactory.insert_list(3, :product)
end

Thanks

1

There are 1 best solutions below

4
On BEST ANSWER

Here's a minimal phoenix application that works with a GenServer started in the application supervisor, using :shared mode for database interactions.

Application Module:

defmodule TestGenServers.Application do
  use Application

  def start(_type, _args) do
    import Supervisor.Spec

    children = [
      supervisor(TestGenServers.Repo, []),
      supervisor(TestGenServers.Web.Endpoint, []),
      worker(TestGenServers.MyServer, [])
    ]

    opts = [strategy: :one_for_one, name: TestGenServers.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

Product Module:

defmodule TestGenServers.Model.Product do
  use Ecto.Schema
  import Ecto.Changeset
  alias TestGenServers.Model.Product


  schema "model_products" do
    field :name, :string

    timestamps()
  end

  @doc false
  def changeset(%Product{} = product, attrs) do
    product
    |> cast(attrs, [:name])
    |> validate_required([:name])
  end
end

GenServer Module:

defmodule TestGenServers.MyServer do
  use GenServer
  alias TestGenServers.Repo

  def start_link() do
    GenServer.start_link(__MODULE__, nil, name: __MODULE__)
  end

  def handle_call({:get_product, id}, _caller, state) do
    {:reply, TestGenServers.Repo.get(TestGenServers.Model.Product, id), state}
  end

end

Test Module:

defmodule TestGenServers.TestMyServer do
  use TestGenServers.DataCase

  setup do
    product = Repo.insert!(%TestGenServers.Model.Product{name: "widget123"})
    %{product_id: product.id}
  end

  test "talk to gen server", %{product_id: id} do
    assert %{id: ^id, name: "widget123"} = GenServer.call(TestGenServers.MyServer, {:get_product, id})
  end
end

DataCase Module

defmodule TestGenServers.DataCase do
  use ExUnit.CaseTemplate

  using do
    quote do
      alias TestGenServers.Repo
      import TestGenServers.DataCase
    end
  end

  setup tags do
    :ok = Ecto.Adapters.SQL.Sandbox.checkout(TestGenServers.Repo)
    unless tags[:async] do
      Ecto.Adapters.SQL.Sandbox.mode(TestGenServers.Repo, {:shared, self()})
    end
    :ok
  end
end

test_helper:

ExUnit.start()

Ecto.Adapters.SQL.Sandbox.mode(TestGenServers.Repo, :manual)