Concurrent Feature Tests with Phoenix and Hound

With the release of Phoenix 1.2 and Ecto 2.0, we now have the ability run automated browser tests concurrently, even ones that that hit the database! I recently spent a couple hours getting this setup on a project so hopefully documenting everything here will save others some time.

We’ll be using Hound for this example, but the setup should be similar for any automated browser tool.

Step 1: Install Hound

Follow the Hound setup instructions

Step 2: Install a driver

The driver determines what browser Hound will interact with during tests.

Here, we’ll be using Selenium to drive Firefox. Other drivers are also available such including chromedriver and phantomjs. See the Hound readme for details.

First, install the driver.

> brew install selenium-server-standalone

Then start the selenium-server daemon. Hound doesn’t start webdriver servers itself, so you’ll need to manage that. Selenium installed via homebrew registers itself as a service.

> brew services start selenium-server-standalone

Configure Hound to use your driver.

# config/test.exs

config :hound, driver: "selenium", browser: "firefox"

Step 3: Turn On the Test Server

This starts up our phoenix endpoint during test runs.

# config/test.exs

config :your_app, YourApp.Endpoint,
  server: true

Step 4: Add the Ecto Sandbox Plug

phoenix_ecto ships with a plug to dynamically switch database transactions for each request, allowing multiple browsers to talk to the same database concurrently.

First, set a flag to enable the sandbox plug in your test config:

# config/test.exs

config :your_app, sql_sandbox: true

Then use the flag to conditionally add add it your endpoint during feature tests.

IMPORTANT: The order of plugs matters, and this one must be listed before any others.

# lib/your_app/endpoint.ex

defmodule YourApp.Endpoint do
  use Phoenix.Endpoint, otp_app: :your_app

  if Application.get_env(:your_app, :sql_sandbox) do
    plug Phoenix.Ecto.SQL.Sandbox
  end

  # ...more plugs
end

Step 4: Define a FeatureCase

We need to define an ExUnit case file to be used by each feature test.

For concurrent tests (ones with async: true) we need to checkout a sandboxed database connection and pass it to Hound.

# test/support/feature_case.ex

defmodule YourApp.FeatureCase do
  use ExUnit.CaseTemplate

  setup tags do
    :ok = Ecto.Adapters.SQL.Sandbox.checkout(YourApp.Repo)

    if tags[:async] do
      metadata = Phoenix.Ecto.SQL.Sandbox.metadata_for(YourApp.Repo, self())
      Hound.start_session(metadata: metadata)
    else
      Hound.start_session
      Ecto.Adapters.SQL.Sandbox.mode(YourApp.Repo, {:shared, self()})
    end

    :ok
  end
end

Step 5: Try It

Create some async feature tests modules (I put them in test/features) with use YourApp.FeatureCase, async: true. Each test module will run in a separate browser instance. That’s pretty cool!

By default, hound sets the number of concurrently running browsers to the number of schedulers initialized by the Erlang VM (usually one per core).

Further Reading

There’s a lot going on under the covers here. If you’re interested in learning more, I recommend checking out the following:

A note on terminology: I use the term feature test to mean an automated test that drives a browser through the application. Some prefer the terms end-to-end test, acceptance test or integration test. For most purposes, these terms are used interchanably.

comments powered by Disqus