ExUnited v0.1.0 ExUnited View Source

This module facilitates spawning nodes within tests.

For using ExUnited, the two essential functions are:

  1. ExUnited.spawn/2 - Spawns (Mix.Config configured, additional code loaded, supervising) nodes
  2. ExUnited.teardown/0 - Kills the spawned nodes and it also cleans up their generated files

The most simplest setup

Nodes can be specified as a list of atoms, just like in the following example. Their node names will be :"bruce@127.0.0.1" and :"clark@127.0.0.1" respectively).

Please do not forget to invoke ExUnited.teardown/0 at the on_exit hook.

setup do
  {:ok, spawned} = ExUnited.spawn([:bruce, :clark])

  on_exit(fn ->
    ExUnited.teardown()
  end)

  spawned
end

"Partially versus Fully connected" and/or "Verbose" spawned nodes

As a second argument, you can pass a list of atoms for the options:

  • :connect - if true a "fully connected" node will be spawned (see the erl -connect_all flag for more information). Defaults to false
  • :verbose - if true the STDOUT of the spawned node will be printed. Defaults to false

See ExUnited.spawn/2 for more information.

setup do
  {:ok, spawned} = ExUnited.spawn([:roy], [:connect, :verbose])

  on_exit(fn ->
    ExUnited.teardown()
  end)

  spawned
end

Which results in the following when running tests:

PME-Legend ~/S/ex_united:master> mix test test/ex_united/supervised_test.exs:140
Excluding tags: [:test]
Including tags: [line: "140"]

iex(roy@127.0.0.1)> Compiling 1 file (.ex)
iex(roy@127.0.0.1)> Generated void app
iex(roy@127.0.0.1)> Interactive Elixir (1.10.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(roy@127.0.0.1)> iex(roy@127.0.0.1)1>
.

Finished in 0.9 seconds
2 tests, 0 failures, 1 excluded

Exclude certain dependencies within spawned nodes

You can exclude certain (Mix) dependencies from your spawned nodes by for instance adding exclude: [:inch_ex] to the options. This can significantly improve the speed of your tests.

setup do
  {:ok, spawned} = ExUnited.spawn([:bruce, :clark], [:verbose, exclude: [:inch_ex]])

  on_exit(fn ->
    ExUnited.teardown()
  end)

  spawned
end

The following dependencies are excluded by default:

  • :credo
  • :dialyxir
  • :ex_doc
  • :ex_united
  • :excoveralls

Configuring the spawned nodes

Aside from the list of atoms, you can also specify nodes as a keyword list in case you want to configure them. The following options are available:

  • :code_paths - a list of directories that will be included
  • :supervise - the child spec(s) used for supervisioning

Including additional code

It would be a best practice to create a directory called test/nodes in which you put a directory containing code for a specific spawned node. Please note that the file called config.exs is supported for Mix.Config:

setup do
  {:ok, spawned} =
    ExUnited.spawn(
      eric: [
        code_paths: [
          "test/nodes/cantona"
        ]
      ]
    )

  on_exit(fn ->
    ExUnited.teardown()
  end)

  spawned
end

See test/ex_united/supervised_test.exs with its corresponding test/nodes/ronaldo as an example.

Add supervisioning

Childspecs should be the same argument as if you are adding them to your classic <app>/application.ex file:

setup do
  {:ok, spawned} =
    ExUnited.spawn(
      bruce: [
        code_paths: [
          "test/nodes/bruce"
        ],
        supervise: [MyAwesomeGenServer]
      ],
      clark: [
        code_paths: [
          "test/nodes/clark"
        ],
        supervise: [MyOtherAwesomeGenServer]
      ]
    )

  on_exit(fn ->
    ExUnited.teardown()
  end)

  spawned
end

Pay attention that functions within childspecs should be quoted.

setup do
  {:ok, spawned} =
    ExUnited.spawn(
      [
        roy: [
          code_paths: [
            "test/nodes/keane"
          ],
          supervise: [
            {
              Roy,
              talk:
                quote do
                  fn
                    1 -> "Hi, I am Roy Keane"
                    2 -> "I am keen as mustard"
                    3 -> "I like to be peachy keen"
                  end
                end
            }
          ]
        ]
      ],
      [:verbose]
    )

  on_exit(fn ->
    ExUnited.teardown()
  end)

  spawned
end

Easily assert and refute within the context of spawned nodes

To seemlessly execute assertions and refutations within spawned nodes, you can setup your test module by either using ExUnited.Case instead of ExUnit.Case:

defmodule MyNodesTest do
  use ExUnited.Case
end

Or by importing the ExUnited.Case module:

defmodule MyNodesTest do
  use ExUnit.Case
  import ExUnited.Case
end

Writing assertions and refutations within the context of a certain spawned is pretty straight forward with the use of the ExUnited.Case.as_node/2 function as if you are writing your class assert and/or refute statements:

defmodule MyNodesTest do
  use ExUnited.Case

  setup do
    {:ok, spawned} = ExUnited.spawn([:bruce, :clark])

    on_exit(fn ->
      ExUnited.teardown()
    end)

    spawned
  end

  test "assertions and refutations within node contexts", spawned do
    bruce = get_in(spawned, [:bruce, :node])

    as_node(bruce) do
      assert :"bruce@127.0.0.1" = Node.self()
      refute :"clark@127.0.0.1" == Node.self()
    end

    as_node(:clark) do
      assert :"clark@127.0.0.1" = Node.self()
      refute :"bruce@127.0.0.1" == Node.self()
    end
  end
end

See ExUnited.Case.as_node/2 for more information.

Link to this section Summary

Functions

Spawns nodes for testing purposes. Supervised applications are supported.

Starts both the ExUnit and ExUnited.Spawn gen servers. This should replace the default ExUnit.start() invocation in the test helper file.

Should be invoked at the end of a test which spawned nodes. This kills the nodes and it also cleans up their generated files located in /tmp/[NODENAME]-{config,mix}.exs.

Link to this section Functions

Link to this function

spawn(nodes, opts \\ [])

View Source
spawn([node()] | [{node(), keyword()}], [atom()]) :: {:ok, [ExUnited.Node.t()]}

Spawns nodes for testing purposes. Supervised applications are supported.

Nodes can be either specified as a list of atoms (such as [:bruce, :clark] for instance; the node names will be :"bruce@127.0.0.1" and :"clark@127.0.0.1" respectively) or as a keyword list (in case of configuring the spawned node).

The following options are available to configure nodes:

  • :code_paths - a list of directories that will be included (please note that the file called config.exs is supported for Mix.Config)
  • :supervise - the child spec(s) used for supervisioning

Aside from options for configuring individual nodes, as a second argument, you can pass a list of atoms for the following:

  • :connect - if true a "fully connected" node will be spawned (see the erl -connect_all flag for more information). Defaults to false
  • :verbose - if true the STDOUT of the spawned node will be printed. Defaults to false

And last but not least, you can exclude certain (Mix) dependencies from your spawned nodes by adding exclude: [:inch_ex] to the options. This can significantly improve the speed of your tests.

The following dependencies are excluded by default:

  • :credo
  • :dialyxir
  • :ex_doc
  • :ex_united
  • :excoveralls

Examples

The most simplest setup:

setup do
  {:ok, spawned} = ExUnited.spawn([:bruce, :clark])

  on_exit(fn ->
    ExUnited.teardown()
  end)

  spawned
end

Spawn "fully connected" nodes and print all their STDOUT in the console:

setup do
  {:ok, spawned} = ExUnited.spawn([:bruce, :clark], [:connect, :verbose])

  on_exit(fn ->
    ExUnited.teardown()
  end)

  spawned
end

Exclude certain dependencies:

setup do
  {:ok, spawned} = ExUnited.spawn([:bruce, :clark], [:verbose, exclude: [:inch_ex]])

  on_exit(fn ->
    ExUnited.teardown()
  end)

  spawned
end

A configured nodes setup:

setup do
  {:ok, spawned} =
    ExUnited.spawn(
      bruce: [
        code_paths: [
          "test/nodes/bruce"
        ],
        supervise: [MyAwesomeGenServer]
      ],
      clark: [
        code_paths: [
          "test/nodes/clark"
        ],
        supervise: [MyOtherAwesomeGenServer]
      ]
    )

  on_exit(fn ->
    ExUnited.teardown()
  end)

  spawned
end

Also note that functions within childspecs should be quoted.

setup do
  {:ok, spawned} =
    ExUnited.spawn(
      [
        roy: [
          code_paths: [
            "test/nodes/keane"
          ],
          supervise: [
            {
              Roy,
              talk:
                quote do
                  fn
                    1 -> "Hi, I am Roy Keane"
                    2 -> "I am keen as mustard"
                    3 -> "I like to be peachy keen"
                  end
                end
            }
          ]
        ]
      ],
      [:verbose]
    )

  on_exit(fn ->
    ExUnited.teardown()
  end)

  spawned
end
Link to this function

start()

View Source
start() :: {:ok, pid()}

Starts both the ExUnit and ExUnited.Spawn gen servers. This should replace the default ExUnit.start() invocation in the test helper file.

# test/test_helper.exs
ExUnited.start()
Link to this function

teardown()

View Source
teardown() :: :ok

Should be invoked at the end of a test which spawned nodes. This kills the nodes and it also cleans up their generated files located in /tmp/[NODENAME]-{config,mix}.exs.

Example

defmodule MyNodesTest do
  use ExUnit.Case

  setup do
    {:ok, spawned} = ExUnited.spawn([:bruce, :clark])

    on_exit(fn ->
      ExUnited.teardown()
    end)

    spawned
  end

  test "does awesome stuff with spawned nodes", spawned do
    # a lot of awesome assertions and refutations
  end
end