Ecto Sandbox использует проверенное соединение для существующего процесса

У меня есть приложение Phoenix Test со схемой продукта. У меня есть GenServer, запущенный главным супервизором приложения, который получает список продуктов с помощью handle_call.

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

Теперь я хочу написать тест для этого GenServer.

Я пробовал сделать что-то подобное в тесте

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

Созданы 3 продукта, я могу найти их в тесте с Repo.all (Product), однако запуск MyGenServer.get_products() вернет пустой массив.

Я не получаю никаких сообщений об ошибках, а просто возвращаю пустой массив, как будто товаров не существует.

Есть ли способ разрешить существующему PID использовать соединение с песочницей оформления заказа и получать мои продукты в процессе GenServer?

PS. Мне удалось запустить тест, перезапустив процесс GenServer в тестовой настройке, но мне было интересно, есть ли более «элегантный» способ решить проблему.

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

Спасибо


person iacobSon    schedule 16.07.2017    source источник
comment
Какова цель вызова GenServer для работы? Почему бы просто не вызвать функцию, чтобы не блокировать каждый запрос к GenServer.   -  person Justin Wood    schedule 17.07.2017
comment
Привет, извините, я не очень понял. Это просто тестовый пример. Я поставил products = Repo.all(Product) просто для простого взаимодействия с базой данных. Итак, это вопрос о поведении Ecto Sandbox, а не о результатах GenServer.   -  person iacobSon    schedule 17.07.2017
comment
Ecto.Adapters.SQL.Sandbox.mode(Repo, :manual) вызывается в test_helper.exs? Вы используете ConnCase или DataCase шаблоны из генератора феникса? Вы проводите тесты с async: true? В противном случае он должен работать автоматически в :shared режиме.   -  person Mike Buhot    schedule 17.07.2017
comment
да для test_helper.exs с использованием DataCase пробовали как с async: true, так и без него, поэтому он не работает в общем режиме , если я не перезапущу процесс, как указано выше. Обратите внимание, что процесс MyGenServer запускается вместе с приложением, поэтому перед: :ok = Ecto.Adapters.SQL.Sandbox.checkout(MyApp.Repo) Все примеры на Ecto Sandbox запускают процессы в тесте, поэтому после процесса владельца (процесс тестирования)   -  person iacobSon    schedule 17.07.2017


Ответы (1)


Вот минимальное приложение Phoenix, которое работает с GenServer, запущенным в супервизоре приложений, используя режим :shared для взаимодействия с базой данных.

Модуль приложения:

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

Модуль продукта:

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:

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

Тестовый модуль:

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

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)
person Mike Buhot    schedule 17.07.2017
comment
Спасибо, Майк! Это очень близко к моему примеру, и мой все еще так не работает. Единственные отличия, которые я сейчас могу заметить, - это то, что я использую ExMachina для генерации данных в тестах, и что я выполняю некоторые запросы, связанные с Repo, в функции init GenServer. Я попробую сегодня вечером удалить ExMachina, чтобы посмотреть, не изменится ли это. У вас есть репозиторий Github с вашим примером выше? Я мог бы попробовать поработать и над этим. - person iacobSon; 18.07.2017
comment
Ах, проблема заключается в доступе к репо в обратном вызове инициализации GenServer. Песочница свяжет соединение с открытой транзакцией с процессом GenServer до того, как test_helper сможет переключить его в ручной режим. - person Mike Buhot; 18.07.2017
comment
Явный вызов Ecto.Adapters.SQL.Sandbox.checkin(Repo) в конце init исправляет тест, но я не уверен, что это лучший способ справиться с этим. - person Mike Buhot; 18.07.2017
comment
Спасибо. Теперь это обретает смысл. Вы знаете какой-либо способ увидеть / найти эту информацию? что-то вроде <#my_gen_server_pid> => is using this connection <#my_test_process_pid> => is using another connection - person iacobSon; 18.07.2017