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.