How to set up the testing environment for a library that uses Ecto?

I want to write a library that people can use together with Ecto.
(To be more precise, I want to write a revisionair adapter for Ecto).

Now this library itself should of course not depend on a certain database setup, as this should be configured by the user that uses the library. However, this is obviously not true while testing.

I am probably not the first to encounter this, but I was unable to find thus far what the proper way of configuring your test environment would be, to ensure that this contains a Repo and a database table to do stuff with.

Help is greatly appreciated!

1 Like

A post was merged into an existing topic: Revisionair: Persistence-agnostic version tracking library

All right, after reading through Ecto’s documentation, the testing source code of multiple libraries and a great conversation with @michalmuskala on Slack today, and I now know a bit more.

Turns out that getting Ecto ready for testing isn’t hard, it just includes a few steps that might not be self-evident:


1) Add/alter elixirc_paths and aliases in your mix.exs:

defmodule Myproject.Mixfile do
  use Mix.Project

  def project do
    [app: :myproject,
     version: "0.1.0",
     elixir: "~> 1.4",
     build_embedded: Mix.env == :prod,
     start_permanent: Mix.env == :prod,
     deps: deps(),
     elixirc_paths: elixirc_paths(Mix.env),
     aliases: aliases()
    ]
  end

  # ...

  # Ensures `test/support/*.ex` files are read during tests
  def elixirc_paths(:test), do: ["lib", "test/support"]
  def elixirc_paths(_), do: ["lib"]

  defp aliases do
    [
      # Ensures database is reset before tests are run
      "test": ["ecto.create --quiet", "ecto.migrate", "test"]
    ]
  end
end

As commented, these respectively ensure that the files in test/support are compiled during testing, and that the database state is reset and created before the tests are run.

###2) Add the Repo file to be used during testing to test/support/repo.ex (or another file in this folder):

defmodule Myproject.Repo do
  use Ecto.Repo, otp_app: :myproject
end

3) In your config.exs, uncomment the include_config "#{Mix.env}.exs" line, and create a test.exs file with the configuration settings you need, which include:

# Repos known to Ecto:
config :myproject, ecto_repos: [Myproject.Repo]

# Test Repo settings
config :myproject, Myproject.Repo,
  adapter: Ecto.Adapters.Postgres,
  username: "postgres",
  password: "postgres",
  database: "myproject_test",
  hostname: "localhost",
  poolsize: 10,
  # Ensure async testing is possible:
  pool: Ecto.Adapters.SQL.Sandbox

Most notably the last line, pool: Ecto.Adapters.SQL.Sandbox, is important. It will allow your database tests to be run concurrently. Read more about the Ecto.Adapters.SQL.Sandbox here.

4) Add the following two lines to your tests_helper.exs:

(or at least, ensure these lines are executed before your database-tests are being run)

{:ok, _pid} = Myproject.Repo.start_link
Ecto.Adapters.SQL.Sandbox.mode(Myproject.Repo, :manual)

5) Above your database-hitting tests, add the following setup code:

defmodule MyprojectTest do
  use ExUnit.Case, async: true # Yes! concurrent tests are possible!
  doctest Myproject

  # Not required, but helpful for test brevity:
  alias Myproject.Repo

  setup do
    # Explicitly get a connection before each test
    :ok = Ecto.Adapters.SQL.Sandbox.checkout(Repo)
  end

  # ... your tests here.

end

This should be enough to get you going. The Ecto generator tasks can be executed by prefixing them with MIX_ENV=test to ensure the testing Repo is found, e.g. MIX_ENV=test mix ecto.gen.migration my_migration_name.

7 Likes