Run the same test suite over multiple setups

I am looking for a smart way to reuse tests.

My project Raxx is a common interface for web servers. So far I have created adapter implementations for two servers cowboy and elli. I am now working an a third adapter for Ace and want to reuse some of my test suits.

For example I have to test the elli adapter.

# test/raxx/adapters/elli/request_test.exs
defmodule Raxx.Elli.RequestTest do
  use ExUnit.Case, async: true

  setup do
    {:ok, _pid} = :elli.start_link [
      callback: Raxx.Adapters.Elli.Handler,
      callback_args: {Raxx.TestSupport.Forwarder, %{target: self()}},
      port: 2020]
    {:ok, %{port: 2020}}

  test "request shows correct method", %{port: port} do
    {:ok, _resp} = HTTPoison.get("localhost:#{port}")
    assert_receive %{method: "GET"}

  # more tests

and similar for the cowboy adapter.

defmodule Raxx.Cowboy.RequestTest do
  use ExUnit.Case, async: true

  setup %{case: case, test: test} do
    name = {case, test}
    routes = [
      {:_, Raxx.Adapters.Cowboy.Handler, {Raxx.TestSupport.Forwarder, %{target: self()}}}
    dispatch = :cowboy_router.compile([{:_, routes}])
    env = [dispatch: dispatch]
    {:ok, _pid} = :cowboy.start_http(name, 2, [port: 0], [env: env])
    {:ok, %{port: :ranch.get_port(name)}}

  test "request shows correct host", %{port: port} do
    {:ok, _resp} = HTTPoison.get("localhost:#{port}")
    assert_receive %{host: "localhost"}

The test suit will be the same for all adapters and only the setup code will vary between them. Is there a smart way to reuse the test suits for different setups?


Take a look at how ecto does the adapter tests. The gist is:

  1. Common tests in integration_tests/{adapter}/ with a structure similar to what would be normally in test/.
  2. Depending on which adapter you want to test, you change the test_paths config option
  3. In each adapter tests use Code.require_file/2 to load and run the appropriate common tests.
1 Like

Yes. I had a similar need recently and ExUnit.CaseTemplate is your friend.

They don’t say this in the docs, but I’ve experimented with this, and it turns out you can also put actual test cases into the template and put the setup code outside the template (i.e. vary it for each different implementation that you create).

So I have a case template file that looks something like this:

defmodule MyApp.MumbleStrategyCase do
  @moduledoc """
  Validates that a MumbleStrategy implementation fulfills
  the basic contract correctly.

  The following pattern will include a set of tests that verifies the MumbleStrategy

defmodule MyMumbleStrategyTest do
use MyApp.MumbleStrategyCase

setup do
  {:ok, mumbler} = # create a mumbler instance
  {:ok, mumbler: mumbler}


use ExUnit.CaseTemplate

using do
  quote location: :keep do

    test "mumbles correctly", %{mumbler: mumbler} do
      assert mumbler.whatever(...)

I guess it is not in the documentation because you have to wrap it in that using block. However even with that it is a nice solution.

Thanks @scouten it took a little bit of tweaking but now have my interface tests against three different webservers, exactly what i needed.

1 Like