0
votes

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
What is the purpose of calling a GenServer to do the work? Why not just call a function, that way you won't block for every request to the GenServer. - Justin Wood
Hi, sorry I was not very clear. That is just a test example. I put the products = Repo.all(Product) just to have a simple interaction with the database. So this is the question about Ecto Sandbox behaviour, not about the GenServer results - iacobSon
Is Ecto.Adapters.SQL.Sandbox.mode(Repo, :manual) is called in the test_helper.exs ? Are you using ConnCase or DataCase templates from the phoenix generator ? Are you running the tests with async: true? If not, then it should work automatically in :shared mode. - Mike Buhot
yes for test_helper.exs using DataCase tried both with async: true and without, so it is not working in shared mode unless I restart the process as stated above. Please note that MyGenServer process is started together with the app, so before the: :ok = Ecto.Adapters.SQL.Sandbox.checkout(MyApp.Repo) All the examples on Ecto Sandbox are starting the processes in the test, so after the owner process (test process) - iacobSon

1 Answers

0
votes

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)