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
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)