Daryl Richter bio photo

Daryl Richter

:: ~winter-paches
:::: Heaven's Cavalry Rider
  :: Ancient Celestial Mariner
  :: Technological Engineer
  :: Modern Reactionary

Email Flickr Github Instagram Stackoverflow Travis CI

Caveat Coder: Althougn I am an experienced coder, I am relatively new to Elixir, so this could easily be “something you shouldn’t do.” All feedback is welcome.

After reading and observing for a while, I finally decided to jump in and create my first test Phoenix app.

Everything was going swimmingly until I added a migration that included some SQL to add a PostgreSQL extension so I could use a citext column.

  def up do
    execute("CREATE EXTENSION IF NOT EXISTS citext;")

Suddenly I was getting errors that looked like this when running my tests:

** (ArgumentError) no extension found for oid `25022`

Fortunately, a search led me to a GH comment which gave me the solution.

The problem was, in the words of ericmj,

“Postgrex fetches types from the server when the first connection to the datasebase is started (when your ecto repo starts) so if you run migrations after the application starts it wont work.”

The solution was to alias the “test” mix command and insert the call to drop and recreate the DB before the application started, like so:

  defp aliases do
    [
      "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
      "ecto.reset": ["ecto.drop",   "ecto.setup"],
      "test":       [&setup_db/1,   "test"]
    ]
  end

  defp setup_db(_) do
    # Create the database, run migrations
    Mix.Task.run "ecto.drop",    ["--quiet"]
    Mix.Task.run "ecto.create",  ["--quiet"]
    Mix.Task.run "ecto.migrate", ["--quiet"]
  end

This worked great, my tests all passed!

Unfortunately, there was a hitch. You see, I like to have my tests running continuously. For this, I have been using mix_test_watch. Because it runs continuously, it tries to re-execute “mix test” while the app is running and thus hits the same error again!

I banged around on this for a while with a couple of dead ends until I finally hit upon a solution. I changed the pre-test method to only recreate the DB if the app isn’t running:

  defp setup_db(_) do
    unless List.keyfind(Application.loaded_applications(), project[:app], 0) do
      # Create the database, run migrations
      Mix.Task.run "ecto.drop",    ["--quiet"]
      Mix.Task.run "ecto.create",  ["--quiet"]
      Mix.Task.run "ecto.migrate", ["--quiet"]
    end
  end

Now, both standalone “mix test” and “mix test.watch” work and as a side benefit, mix_test_watch is a bit faster!