Setting up Ecto 2 in Test Environment

I’m currently working on writing a package that uses Ecto 2, but not phoenix. I needed to have my test database work the way Rails does: isolating testing to a specific test database, and ensuring the test database is clean after every run.

I tried modifying some Ecto 1 test_helper examples but was getting weird errors like this:

** (exit) exited in: GenServer.call(Ecto.Migration.Supervisor, {:start_child, [#PID<0.144.0>, TestRepo, :forward, :up, false]}, :infinity)
    ** (EXIT) no process
           (elixir) lib/gen_server.ex:564: GenServer.call/3
                    lib/ecto/migration/runner.ex:22: Ecto.Migration.Runner.run/6
                    lib/ecto/migrator.ex:121: Ecto.Migrator.attempt/6
                    lib/ecto/migrator.ex:71: anonymous fn/4 in Ecto.Migrator.do_up/4
                    lib/ecto/adapters/sql.ex:472: anonymous fn/3 in Ecto.Adapters.SQL.do_transaction/3
    (db_connection) lib/db_connection.ex:973: DBConnection.transaction_run/4
    (db_connection) lib/db_connection.ex:897: DBConnection.run_begin/3
    (db_connection) lib/db_connection.ex:671: DBConnection.transaction/3

I then realized that Ecto 2 treats things a bit differently than Ecto 1. The following outlines the steps I took to setup a test database using Postgres in Ecto 2.

First, we need to be able to specify special configuration values just for our test environment. If you don’t have this already, put the following at the bottom of config/config.exs.

if Mix.env == :test do
  import_config "test.exs"
end

Now, we need to specify the connection details of our test database. We also need to tell the database to clear between test runs. Create the config/test.exs file with the following.

use Mix.Config
config :servant, TestRepo,
  adapter: Ecto.Adapters.Postgres,
  username: "postgres",
  password: "postgres",
  database: "servant_test",
  hostname: "localhost",
  pool: Ecto.Adapters.SQL.Sandbox,
  priv: "test/support" # only needed if your project does not have specific migrations

config :servant, ecto_repos: [TestRepo]

See Making Sense of Ecto 2 SQL.Sandbox and Connection Ownership Modes for more information on Ecto.Adapters.SQL.Sandbox.

We now need to modify our mix.exs file to autoload the test/support files we will be using, as well as have it run create the test database and run migrations when running tests.

Modify your mix.exs file as follows:

  def project do
    [...
     elixirc_paths: elixirc_paths(Mix.env),
     ...
     aliases: aliases
    ]
  end

  defp elixirc_paths(:test), do: ["lib", "test/support"]
  defp elixirc_paths(_), do: ["lib"]

  defp aliases do
    ["test": ["ecto.create --quiet", "ecto.migrate", "test"]]
  end

Now, I am going to setup a Test repo to interact with the test database. Create the file test/support/repo.ex and add the following:

defmodule TestRepo do
  use Ecto.Repo, otp_app: :servant
end

Now, since my elixir project doesn’t have any actual migrations in `lib`, I need to create a migration to be used in the testing environment. I added the following in test/support/migrations/migrations.exs:

NOTE If you have migrations in your actual project, you can alternatively specify a directory to real migrations in the priv value set in mix.exs at the beginning of this post.

defmodule TestMigration do
  use Ecto.Migration

  def change do
    ....
  end
end

We now have to modify our test/test_helper.exs to use the SQL Sandbox mode to rollback all changes. See:

ExUnit.start()

defmodule Servant.TestCase do
  use ExUnit.CaseTemplate

  setup do
    # Explicitly get a connection before each test
    # By default the test is wrapped in a transaction
    :ok = Ecto.Adapters.SQL.Sandbox.checkout(TestRepo)

    # The :shared mode allows a process to share
    # its connection with any other process automatically
    Ecto.Adapters.SQL.Sandbox.mode(TestRepo, { :shared, self() })
  end
end


{:ok, _pid} = TestRepo.start_link
Ecto.Adapters.SQL.Sandbox.mode(TestRepo, { :shared, self() })

Now, I just have to ensure that my tests are doing using

use Servant.TestCase

instead of

use ExUnit.Case

and we should be good to go.

Was this useful? Did this work for you? Let me know in the comments.

Leave a Reply

Your email address will not be published. Required fields are marked *