ExUnit.ClusteredCase (ex_unit_clustered_case v0.5.0)
Helpers for defining clustered test cases.
Use this in place of ExUnit.Case
when defining test modules where
you will be defining clustered tests. Elixir.ExUnit.ClusteredCase
extends
ExUnit.Case
to provide additional helpers for those tests.
Since Elixir.ExUnit.ClusteredCase
is an extension of ExUnit.Case
, it takes the
same options, and imports the same test helpers and callbacks. It adds
new helpers, scenario/3
, node_setup/1
, and node_setup/2
, and aliases
the Elixir.ExUnit.ClusteredCase.Cluster
module for convenience.
examples
Examples
defmodule KVStoreTests do
# Use the module
use ExUnit.ClusteredCase
# Define a clustered scenario
scenario "given a healthy cluster", [cluster_size: 3] do
# Set up each node in the cluster prior to each test
node_setup do
{:ok, _} = Application.ensure_all_started(:kv_store)
end
# Define a test to run in this scenario
test "always pass" do
assert true
end
end
end
context
Context
All tests receive a context as an argument, just like with ExUnit.Case
, the
primary difference to be aware of is that the context contains a key, :cluster
,
which is the pid of the cluster manager, and is used to invoke functions in the
Elixir.ExUnit.ClusteredCase.Cluster
module during tests.
defmodule KVStoreTests do
use ExUnit.ClusteredCase
scenario "given a healthy cluster", [cluster_size: 3] do
# You can use normal setup functions to setup context for the
# test, this is run once prior to each test
setup do
{:ok, foo: :bar}
end
# Like `setup`, but is run on all nodes prior to each test
node_setup do
{:ok, _} = Application.ensure_all_started(:kv_store)
end
test "cluster has three nodes", %{cluster: c} = context do
assert length(Cluster.members(c)) == 3
end
end
end
See the ExUnit.Case
documentation for information on tags, filters, and more.
Link to this section Summary
Functions
Like ExUnit.Callbacks.setup/1
, but is executed on every node in the cluster.
Same as node_setup/1
, but receives the test setup context as a parameter.
Creates a new clustered test scenario.
Link to this section Functions
Like ExUnit.Callbacks.setup/1
, but is executed on every node in the cluster.
You can pass a block, a unary function as an atom, or a list of such atoms.
If you pass a unary function, it receives the test setup context, however unlike
setup/1
, the value returned from this function does not modify the context. Use
setup/1
or setup/2
for that.
NOTE: This callback is invoked on each node in the cluster for the given scenario.
examples
Examples
def start_apps(_context) do
{:ok, _} = Application.ensure_all_started(:kv_store)
:ok
end
scenario "given a healthy cluster", [cluster_size: 3] do
node_setup :start_apps
node_setup do
# This form is also acceptable
{:ok, _} = Application.ensure_all_started(:kv_store)
end
end
Same as node_setup/1
, but receives the test setup context as a parameter.
examples
Examples
scenario "given a healthy cluster", [cluster_size: 3] do
node_setup _context do
# Do something on each node
end
end
Creates a new clustered test scenario.
Usage of this macro is similar to that of ExUnit.Case.describe/2
,
but has some differences. While describe/2
simply groups tests under a
common description, scenario/3
both describes the group of tests, and
initializes a cluster which will be made available for each test in that scenario.
NOTE: It is important to be aware that each scenario is a distinct cluster, and that all tests within a single scenario are running against the same cluster. If tests within a scenario may conflict with one another - perhaps by modifying shared state, or triggering crashes which may bring down shared processes, etc., then you have a couple options:
- Disable async testing for the module
- Modify your tests to prevent conflict, e.g. writing to different keys in a k/v store, rather than the same key
- Split the scenario into many, where the tests can run in isolation.
options
Options
You can configure a scenario with the following options:
cluster_size: integer
, will create a cluster of the given size, this option is mutually exclusive with:nodes
, if the latter is used, this option will be ignored.nodes: [[node_opt]]
, a list of node specifications to use when creating the cluster, seeElixir.ExUnit.ClusteredCase.Node.node_opt/0
for specific options available. If used,:cluster_size
is ignored.env: [{String.t, String.t}]
, will set the given key/values in the environment when creating nodes. If you need different values for each node, you will need to use:nodes
erl_flags: [String.t]
, additional arguments to pass toerl
when creating nodes, like:env
, if you need different args for each node, you will need to use:nodes
config: Keyword.t
, configuration overrides to apply to all nodes in the clusterboot_timeout: integer
, the amount of time to allow for nodes to boot, in millisecondsinit_timeout: integer
, the amount of time to allow for nodes to be initialized, in milliseconds
examples
Examples
defmodule KVStoreTest do
use ExUnit.ClusteredCase
@scenario_opts [cluster_size: 3]
scenario "given a healthy cluster", @scenario_opts do
node_setup do
{:ok, _} = Application.ensure_all_started(:kv_store)
end
test "writes are replicated to all nodes", %{cluster: cluster} do
writer = Cluster.random_member(cluster)
key = self()
value = key
assert Cluster.call(writer, KVStore, :put, [key, value]) == :ok
results = Cluster.map(cluster, KVStore, :get, [key])
assert Enum.all?(results, fn val -> val == value end)
end
end
end
Since all scenarios are also describes, you can run all the tests for a scenario by it's description:
mix test --only describe:"given a healthy cluster"
or by passing the exact line the scenario starts on:
mix test path/to/file:123
Like describe/2
, you cannot nest scenario/3
. Use the same technique
of named setups recommended in the describe/2
documentation for composition.