Using setup_all with database?

I would like to use the setup_all macro with one of my test modules. In that setup_all I want to create some users and send them to tests. Unfortunately I can’t make it work.

This is a phoenix 1.3 application

here is my setup_all

setup_all do
    %{user: fixture(:user)}
end

where fixture(:user) calls my Phoenix context function which essentially does

Repo.insert(user_changeset, returning: true)

and here is the result

  3) Qssite.Schema.Mutation.CreateResetTokenTest: failure on setup_all callback, test invalidated
     ** (DBConnection.OwnershipError) cannot find ownership process for #PID<0.397.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.

     stacktrace:
       (db_connection) lib/db_connection.ex:926: DBConnection.checkout/2
       (db_connection) lib/db_connection.ex:742: DBConnection.run/3
       (db_connection) lib/db_connection.ex:790: DBConnection.transaction/3
       (qssite) lib/qssite/accounts/accounts.ex:32: Qssite.Accounts.create_user/1
       (qssite) test/support/custom_helpers.ex:21: Qssite.Test.CustomHelpers.fixture/2
       test/qssite_web/schema/mutation/create_reset_token_test.exs:8: Qssite.Schema.Mutation.CreateResetTokenTest.__ex_unit_setup_all_1/1
       test/qssite_web/schema/mutation/create_reset_token_test.exs:1: Qssite.Schema.Mutation.CreateResetTokenTest.__ex_unit__/2

I don’t understand what exactly it wants me to do. I understand setup_all runs in a separate process than this module but I don’t know how to handle this.

1 Like

Out of curiosity, why not put these in a regular setup block?

Initially I had it in a setup block but there are 3 reasons:

  1. setup runs for every test case and I wan’t to do some tests that involve multiple users. So maybe cut some testing time by not having to create the users every time.

  2. setup_all runs on a separate process. Every time a user is created I get a {:delivered_email, welcome_email} message from Bamboo in the current process. In this module I test only {:delivered_email, password_reset_email}. I wan’t to see if messages related to welcome_email go to the setup_all process. Mostly curiosity than a need to keep the job done. My tests work anyway. Actually this is not a big deal but is directly related with

  3. I wan’t learn how to use setup_all and see how it works. :smile:

3 Likes

I made progress with this. Thanks to this blog post

In case someone stumbles in this problem here is what worked for me

in my setup_all block I added these lines in the top

:ok = Ecto.Adapters.SQL.Sandbox.checkout(Repo)
    Ecto.Adapters.SQL.Sandbox.mode(Repo, :auto)

Now I need to research a good cleanup strategy :smiley:

7 Likes

My use case is when using with Triplex, in every test i need a “tenant” to exist. The default setup where a tenant is created in every test is unacceptably slow (each test takes >1 second).

My solution is to make a new module called TenantCase:

defmodule MyApp.TenantCase do
  use ExUnit.CaseTemplate

  setup_all tags do
    :ok = Ecto.Adapters.SQL.Sandbox.checkout(MyApp.Repo)
    # we are setting :auto here so that the data persists for all tests,
    # normally (with :shared mode) every process runs in a transaction
    # and rolls back when it exits. setup_all runs in a distinct process
    # from each test so the data doesn't exist for each test.
    Ecto.Adapters.SQL.Sandbox.mode(MyApp.Repo, :auto)
    {:ok, tenant} = MyApp.Tenant.create(%{name: "example"})

    on_exit fn ->
      # this callback needs to checkout its own connection since it
      # runs in its own process
      :ok = Ecto.Adapters.SQL.Sandbox.checkout(MyApp.Repo)
      Ecto.Adapters.SQL.Sandbox.mode(MyApp.Repo, :auto)

      # we also need to re-fetch the %Tenant struct since Ecto otherwise
      # complains it's "stale"
      tenant = MyApp.Tenant.get!(tenant.id)
      MyApp.Tenant.delete(tenant)
      :ok
    end

    [tenant: tenant]
  end
end

Now i can use the tenant in my tests:

defmodule MyApp.MyTest do
  use MyApp.ConnCase
  use MyApp.TenantCase

  test "foo", %{tenant: tenant} do
    ...
  end
end
20 Likes

These lines could have been in the error message! Really simple solution.
Anyway, we can’t be too hard on that subject, elixir error messages are great!

Thanks for this!

Before:

Finished in 14.8 seconds
154 tests, 0 failures

After:

Finished in 4.9 seconds
154 tests, 0 failures

:partying_face:

4 Likes

so given the answers here, it was not clear to me why

      Ecto.Adapters.SQL.Sandbox.mode(MyApp.Repo, :auto)

requires cleanup through on_exit callback, but the article linked by @voger made it easier to understand: