Supertester User Manual
View SourceVersion: 0.5.1
Welcome to the comprehensive user manual for Supertester. This document provides a detailed overview of all modules and functions available in the Supertester toolkit.
Table of Contents
- Introduction
- Core Concepts
- Installation
- Core Modules
- OTP Testing Helpers
- Chaos Engineering
- Performance Testing
- Concurrency Harness
- Telemetry & Diagnostics
- Custom Assertions
- Practical Examples & Recipes
- Best Practices
- Troubleshooting
Introduction
Supertester is a battle-hardened OTP testing toolkit designed to help you build robust and reliable Elixir applications. It provides a comprehensive suite of tools for testing concurrent systems, including features for chaos engineering, performance testing, and zero-sleep synchronization.
This manual will guide you through the features and best practices for using Supertester to its full potential.
Core Concepts
Test Isolation
Supertester provides robust test isolation, allowing you to run your tests concurrently (async: true) without worrying about process name collisions or state leakage. This is achieved through the Supertester.UnifiedTestFoundation runtime and its ExUnit adapter, Supertester.ExUnitFoundation, which create a sandboxed environment for each test.
Zero Process.sleep
Timing-based synchronization (Process.sleep/1) is a common source of flaky tests. Supertester eliminates the need for this by providing deterministic synchronization patterns, such as cast_and_sync/3, which ensures that an asynchronous operation has completed before the test proceeds.
Automatic Cleanup
All resources created using Supertester's helpers (e.g., setup_isolated_genserver/3) are automatically cleaned up at the end of each test. This prevents resource leaks and ensures that tests do not interfere with each other.
Expressive Assertions
Supertester includes a rich set of OTP-aware assertions that make your tests more expressive and easier to read. For example, assert_genserver_state/2 allows you to assert on the internal state of a GenServer without manually fetching it.
Installation
To get started with Supertester, add it as a dependency in your mix.exs file. It's only required for the :test environment.
def deps do
[
{:supertester, "~> 0.5.1", only: :test}
]
endThen, run mix deps.get to install the dependency.
Core Modules
Supertester
The main module provides basic information about the library.
version()
Returns the current version of the Supertester library.
- Signature:
@spec version() :: String.t() - Example:
Supertester.version() #=> "0.5.1"
Supertester.ExUnitFoundation
This module is the drop-in ExUnit adapter for Supertester isolation. Replace use ExUnit.Case with it to automatically configure the appropriate async setting and install the isolation setup callback.
Usage:
defmodule MyApp.MyTest do
use Supertester.ExUnitFoundation, isolation: :full_isolation
test "an isolated test", context do
# `context.isolation_context` contains isolation information.
# All processes started with Supertester helpers are tracked and cleaned up.
end
endIsolation Modes (:isolation option):
-
:basic: Provides basic isolation with unique process naming (async-friendly). -
:registry: Uses a dedicated registry for process isolation (async-friendly). -
:full_isolation: Provides complete process and ETS table isolation. This is the recommended mode (async-friendly). -
:contamination_detection: Detects if a test leaks processes or ETS tables (runs synchronously).
Additional Isolation Options:
-
telemetry_isolation: trueenablesSupertester.TelemetryHelpers. -
logger_isolation: trueenablesSupertester.LoggerIsolation. -
ets_isolation: [...]mirrors named ETS tables into isolated copies. -
@tag telemetry_events: [...]auto-attaches isolated telemetry handlers. -
@tag ets_tables: [...]mirrors tables for a single test. -
@tag logger_level: :debugoverrides the process log level for the test.
defmodule MyApp.MyTest do
use Supertester.ExUnitFoundation,
isolation: :full_isolation,
telemetry_isolation: true,
logger_isolation: true,
ets_isolation: [:my_table]
@tag telemetry_events: [[:supertester, :concurrent, :scenario, :stop]]
@tag logger_level: :debug
test "isolation extensions", _context do
# ...
end
endSupertester.UnifiedTestFoundation
Supertester.UnifiedTestFoundation now focuses on the isolation runtime itself. Use it directly when integrating Supertester with a custom harness or non-ExUnit environment. The legacy use Supertester.UnifiedTestFoundation macro is still available but emits a compile-time warning—prefer Supertester.ExUnitFoundation for ExUnit integration.
Manual usage example:
defmodule CustomHarnessTest do
use ExUnit.Case, async: true
setup context do
Supertester.UnifiedTestFoundation.setup_isolation(:full_isolation, context)
end
test "custom integration", context do
assert match?(%Supertester.IsolationContext{}, context.isolation_context)
end
endThe value stored in context.isolation_context is a %Supertester.IsolationContext{} struct that captures the test_id, tracked processes, ETS tables, and contextual tags (module, test name, isolation mode, etc.), making it easy to log or inspect diagnostics.
Supertester.Env
Supertester.Env abstracts how Supertester registers cleanup callbacks. By default it delegates to ExUnit.Callbacks.on_exit/1, but you can plug in a custom module (that implements the Supertester.Env behaviour) for other harnesses:
defmodule MyHarness.Env do
@behaviour Supertester.Env
@impl true
def on_exit(fun) do
MyHarness.register_cleanup(fun)
end
end
# config/test.exs
import Config
config :supertester, :env_module, MyHarness.EnvSupertester.TestableGenServer
This behavior injects a __supertester_sync__ handler into your GenServers, enabling deterministic testing of asynchronous operations without Process.sleep/1.
Usage in your GenServer:
defmodule MyApp.MyServer do
use GenServer
use Supertester.TestableGenServer
# Your GenServer implementation...
endUsage in your tests:
test "testing an async operation" do
{:ok, server} = MyApp.MyServer.start_link()
# Perform an async operation
GenServer.cast(server, :some_async_work)
# Wait for the operation to complete
:ok = GenServer.call(server, :__supertester_sync__)
# Now it's safe to assert the state
assert_genserver_state(server, fn state -> state.work_done == true end)
endThe injected handler also supports returning the state directly:
{:ok, state} = GenServer.call(server, {:__supertester_sync__, return_state: true})OTP Testing Helpers
Supertester.OTPHelpers
This module contains core helpers for testing OTP-compliant processes.
setup_isolated_genserver(module, test_name \\ "", opts \\ [])
Starts an isolated GenServer with a unique name and automatic cleanup.
Signature:
@spec setup_isolated_genserver(module(), String.t(), keyword()) :: {:ok, pid()} | {:error, term()}- Parameters:
-
module: TheGenServermodule to start. -
test_name(optional): A name for the test context to ensure unique process naming. -
opts(optional): Options to pass toGenServer.start_link/3.
-
- Example:
{:ok, server} = setup_isolated_genserver(MyServer, "my_test", [initial_state: %{}])
setup_isolated_supervisor(module, test_name \\ "", opts \\ [])
Starts an isolated Supervisor with a unique name and automatic cleanup.
Signature:
@spec setup_isolated_supervisor(module(), String.t(), keyword()) :: {:ok, pid()} | {:error, term()}- Example:
{:ok, supervisor} = setup_isolated_supervisor(MySupervisor, "my_supervisor_test")
wait_for_genserver_sync(server, timeout \\ 1000)
Waits for a GenServer to be alive and responsive.
Signature:
@spec wait_for_genserver_sync(GenServer.server(), timeout()) :: :ok | {:error, term()}- Example:
wait_for_genserver_sync(server_pid)
wait_for_process_restart(process_name, original_pid, timeout \\ 1000)
Waits for a supervised process to be terminated and restarted.
Signature:
@spec wait_for_process_restart(atom(), pid(), timeout()) :: {:ok, pid()} | {:error, term()}- Example:
original_pid = GenServer.whereis(MyServer) GenServer.stop(MyServer) {:ok, new_pid} = wait_for_process_restart(MyServer, original_pid)
Supertester.GenServerHelpers
This module provides helpers specifically for testing GenServers.
get_server_state_safely(server)
Fetches the state of a GenServer without crashing if the process is down.
Signature:
@spec get_server_state_safely(GenServer.server()) :: {:ok, term()} | {:error, term()}- Example:
{:ok, state} = get_server_state_safely(server_pid)
cast_and_sync(server, cast_message, sync_message \\ :__supertester_sync__, opts \\ [])
Sends a cast message and then waits for a follow-up call to confirm the cast was processed. This is the recommended way to test async operations.
Signature:
@spec cast_and_sync(GenServer.server(), term(), term(), keyword()) :: :ok | {:ok, term()} | {:error, term()}- Options:
:strict?(defaultfalse) raises when the target server doesn't implement the sync handler;:timeoutto customize the sync call timeout. - Example:
:ok = cast_and_sync(counter_pid, :increment) assert_genserver_state(counter_pid, fn state -> state.count == 1 end) # Enforce the presence of the sync handler assert_raise ArgumentError do cast_and_sync(counter_pid, :increment, :__supertester_sync__, strict?: true) end
test_server_crash_recovery(server, crash_reason)
Simulates a process crash and verifies its recovery by its supervisor.
Signature:
@spec test_server_crash_recovery(GenServer.server(), term()) :: {:ok, map()} | {:error, term()}- Example:
{:ok, info} = test_server_crash_recovery(server_pid, :test_crash) assert info.recovered == true
concurrent_calls(server, calls, count \\ 10, opts \\ [])
Stress-tests a GenServer with many concurrent requests.
- Signature:
@spec concurrent_calls(GenServer.server(), [term()], pos_integer(), keyword()) :: {:ok, [map()]}(opts[:timeout]controls the per-call timeout) - Example:
calls = [:get_counter, {:increment, 1}] {:ok, results} = concurrent_calls(server_pid, calls, 20, timeout: 50) Enum.each(results, fn %{call: call, successes: successes, errors: errors} -> IO.inspect({call, length(successes), length(errors)}) end)
Supertester.SupervisorHelpers
This module provides helpers for testing supervision trees and restart strategies.
test_restart_strategy(supervisor, strategy, scenario)
Tests a supervisor's restart strategy (:one_for_one, :one_for_all, etc.) with a given failure scenario.
- Signature:
@spec test_restart_strategy(Supervisor.supervisor(), atom(), restart_scenario()) :: test_result() - Example:
result = test_restart_strategy(supervisor, :one_for_one, {:kill_child, :worker_1}) assert result.restarted == [:worker_1] assert :worker_2 in result.not_restarted
assert_supervision_tree_structure(supervisor, expected)
Validates the structure of a supervision tree.
- Signature:
@spec assert_supervision_tree_structure(Supervisor.supervisor(), tree_structure()) :: :ok - Example:
assert_supervision_tree_structure(root_supervisor, %{ supervisor: RootSupervisor, strategy: :one_for_one, children: [ {:cache, CacheServer}, {:worker_pool, WorkerPoolSupervisor} ] })
trace_supervision_events(supervisor, opts \\ [])
Monitors and returns all supervision events (e.g., child started, terminated, restarted) that occur during a test.
- Signature:
@spec trace_supervision_events(Supervisor.supervisor(), keyword()) :: {:ok, (-> [supervision_event()])} - Example:
{:ok, stop_trace} = trace_supervision_events(supervisor) # ... cause a failure ... events = stop_trace.() assert Enum.any?(events, &match?({:child_restarted, _, _, _}, &1))
wait_for_supervisor_stabilization(supervisor, timeout \\ 5000)
Waits for a supervisor to have all its children running and stable, which is useful after inducing failures.
Signature:
@spec wait_for_supervisor_stabilization(Supervisor.supervisor(), timeout()) :: :ok | {:error, :timeout}- Example:
# ... cause chaos ... :ok = wait_for_supervisor_stabilization(supervisor) assert_all_children_alive(supervisor)
Chaos Engineering
Supertester.ChaosHelpers
This module provides a toolkit for chaos engineering to test the resilience of your system.
inject_crash(target, crash_spec, opts \\ [])
Injects a controlled crash into a process.
- Signature:
@spec inject_crash(pid(), crash_spec(), keyword()) :: :ok - Crash Specifications:
-
:immediate: Crashes the process immediately. -
{:after_ms, duration}: Crashes after a delay. -
{:random, probability}: Crashes with a given probability (0.0 to 1.0).
-
- Example:
inject_crash(worker_pid, :immediate) inject_crash(worker_pid, {:random, 0.5}) # 50% chance of crash
chaos_kill_children(supervisor, opts \\ [])
Randomly kills children in a supervision tree to test restart strategies and system resilience.
- Signature:
@spec chaos_kill_children(Supervisor.supervisor(), keyword()) :: chaos_report() - Options:
:kill_rate,:duration_ms,:kill_interval_ms - Example:
report = chaos_kill_children(supervisor, kill_rate: 0.5, duration_ms: 3000) assert report.supervisor_crashed == false
simulate_resource_exhaustion(resource, opts \\ [])
Simulates resource exhaustion scenarios, such as process or ETS table limits.
Signature:
@spec simulate_resource_exhaustion(atom(), keyword()) :: {:ok, cleanup_fn :: (-> :ok)} | {:error, term()}- Resources:
:process_limit,:ets_tables,:memory - Example:
{:ok, cleanup} = simulate_resource_exhaustion(:process_limit, spawn_count: 1000) # ... perform tests under pressure ... cleanup.()
assert_chaos_resilient(system, chaos_fn, recovery_fn, opts \\ [])
Asserts that a system recovers from a chaos scenario within a given timeout.
- Signature:
@spec assert_chaos_resilient(pid(), (-> any()), (-> boolean()), keyword()) :: :ok - Example:
assert_chaos_resilient(supervisor, fn -> chaos_kill_children(supervisor, kill_rate: 0.5) end, fn -> all_workers_are_healthy?(supervisor) end, timeout: 10_000 )
run_chaos_suite(target, scenarios, opts \\ [])
Executes a list of chaos scenarios—now including full Supertester.ConcurrentHarness scenarios—
against the same target process or supervisor so you can orchestrate concurrent workloads while
injecting faults.
- Signature:
@spec run_chaos_suite(pid(), [map()], keyword()) :: chaos_suite_report() - Concurrent Scenarios: Provide
%{type: :concurrent, build: fn target -> scenario end}or%{type: :concurrent, scenario: <ConcurrentHarness scenario>}entries to reuse the harness with shared telemetry/reporting. - Example:
scenarios = [ %{type: :kill_children, kill_rate: 0.4, duration_ms: 500}, %{ type: :concurrent, build: fn supervisor -> Supertester.ConcurrentHarness.simple_genserver_scenario( MyWorker, [{:cast, :do_work}, {:call, :get_state}], 3, setup: fn -> {:ok, supervisor, %{}} end, cleanup: fn _, _ -> :ok end ) end } ] report = run_chaos_suite(supervisor, scenarios, timeout: 10_000) assert report.failed == 0
Performance Testing
Supertester.PerformanceHelpers
This module provides tools for performance testing and regression detection.
assert_performance(operation, expectations)
Asserts that an operation meets specific performance bounds.
- Signature:
@spec assert_performance((-> any()), keyword()) :: :ok - Expectations:
:max_time_ms,:max_memory_bytes,:max_reductions - Example:
assert_performance( fn -> APIServer.get_user(1) end, max_time_ms: 50, max_memory_bytes: 500_000 )
assert_no_memory_leak(iterations, operation, opts \\ [])
Detects memory leaks by running an operation many times and checking for memory growth.
- Signature:
@spec assert_no_memory_leak(pos_integer(), (-> any()), keyword()) :: :ok - Example:
assert_no_memory_leak(10_000, fn -> MessageWorker.process(worker, generate_message()) end)
measure_operation(operation)
Measures the performance metrics of an operation.
- Signature:
@spec measure_operation((-> any())) :: map() - Returns: A map with
:time_us,:memory_bytes,:reductions, and:result. - Example:
metrics = measure_operation(fn -> complex_calculation() end) IO.inspect(metrics)
assert_mailbox_stable(server, opts)
Asserts that a GenServer's mailbox does not grow uncontrollably during an operation.
- Signature:
@spec assert_mailbox_stable(pid(), keyword()) :: :ok - Options:
:during(the function to execute),:max_size(max allowed mailbox size). - Example:
assert_mailbox_stable(server, during: fn -> send_many_messages(server, 1000) end, max_size: 50 )
Concurrency Harness
Supertester.ConcurrentHarness
The concurrency harness lets you describe complex multi-threaded scenarios declaratively.
Provide a setup function, thread scripts (lists of operations), optional mailbox monitoring,
chaos hooks, performance expectations, and an invariant function. run/1 coordinates each thread,
synchronizes casts with Supertester.TestableGenServer, records every event, emits telemetry, and
returns a diagnostic report:
scenario =
Supertester.ConcurrentHarness.simple_genserver_scenario(
CounterServer,
[{:cast, :increment}, {:call, :value}],
4,
mailbox: [sampling_interval: 1],
chaos: Supertester.ConcurrentHarness.chaos_inject_crash(),
performance_expectations: [max_time_ms: 100],
invariant: fn server, ctx ->
{:ok, state} = Supertester.GenServerHelpers.get_server_state_safely(server)
assert state.count >= 0
assert ctx.metrics.total_operations > 0
end
)
assert {:ok, report} = Supertester.ConcurrentHarness.run(scenario)Use from_property_config/3 to convert property-test generators straight into runnable scenarios.
Scenario metadata automatically includes a :scenario_id, and every run emits
[:supertester, :concurrent, :scenario, :start|:stop] telemetry events for observability.
Additional helpers:
chaos_kill_children/1/chaos_inject_crash/2– build ready-made chaos hooks.run_with_performance/2– wrap an ad-hoc scenario with performance bounds outside of the struct.
Supertester.PropertyHelpers
PropertyHelpers builds on StreamData to emit normalized operations and scenario configs that the
concurrent harness understands. genserver_operation_sequence/2 normalizes operations into tagged
{:call, term} / {:cast, term} tuples, while concurrent_scenario/1 generates complete configs:
use ExUnitProperties
property "counter invariants hold" do
generator =
Supertester.PropertyHelpers.concurrent_scenario(
operations: [{:cast, :increment}, {:cast, :decrement}, {:call, :value}],
min_threads: 1,
max_threads: 3
)
check all cfg <- generator do
scenario = Supertester.ConcurrentHarness.from_property_config(CounterServer, cfg)
assert {:ok, _report} = Supertester.ConcurrentHarness.run(scenario)
end
endIf :stream_data is not present in your project, these helpers raise a clear error suggesting the
dependency addition.
Supertester.MessageHarness
MessageHarness.trace_messages/3 snapshots a process mailbox, enables :erlang.trace for
:receive events, runs your function, then returns the captured messages and result. It is
ideal for debugging concurrency issues without changing application code.
report =
Supertester.MessageHarness.trace_messages(server, fn ->
send(server, {:direct, :hello})
end)
assert {:direct, :hello} in report.messagesTelemetry & Diagnostics
Supertester.Telemetry
Supertester emits :telemetry events under the [:supertester | ...] namespace. The helper
module centralizes emission so you can subscribe once and observe:
[:supertester, :concurrent, :scenario, :start|:stop]– scenario lifecycle with duration/status.[:supertester, :concurrent, :mailbox, :sample]– mailbox measurements collected during runs.[:supertester, :chaos, :start|:stop]– chaos hooks firing, including duration and failures.[:supertester, :performance, :scenario, :measured]– performance metrics captured for scenarios.
Attach handlers with :telemetry.attach/4 or attach_many/4:
:telemetry.attach(
"supertester-console",
[:supertester, :concurrent, :scenario, :stop],
fn _event, %{duration_ms: duration}, metadata, _ ->
Logger.info("Scenario #{metadata.scenario_id} took #{duration}ms (#{metadata[:status] || :ok})")
end,
nil
)Supertester.TelemetryHelpers
TelemetryHelpers provides per-test telemetry isolation, so your tests only receive
events tagged with the current test id. This prevents cross-test noise when running
async: true.
{:ok, _test_id} = Supertester.TelemetryHelpers.setup_telemetry_isolation()
{:ok, _handler} = Supertester.TelemetryHelpers.attach_isolated([:my, :event])
Supertester.TelemetryHelpers.emit_with_context([:my, :event], %{value: 1}, %{})
assert Supertester.TelemetryHelpers.assert_telemetry([:my, :event])Key helpers:
-
setup_telemetry_isolation/0andsetup_telemetry_isolation/1 -
attach_isolated/2withpassthrough,buffer,filter_key, andtransform -
assert_telemetry/1-3,refute_telemetry/2,assert_telemetry_count/2,flush_telemetry/1 -
with_telemetry/2andemit_with_context/3
Supertester.LoggerIsolation
LoggerIsolation provides process-scoped logger levels and log capture helpers.
:ok = Supertester.LoggerIsolation.setup_logger_isolation()
Supertester.LoggerIsolation.isolate_level(:debug)
{log, _result} =
Supertester.LoggerIsolation.capture_isolated(:debug, fn ->
Logger.debug("hello")
:ok
end)Key helpers:
-
setup_logger_isolation/0andsetup_logger_isolation/1 -
isolate_level/1,restore_level/0,get_isolated_level/0 -
capture_isolated/2,capture_isolated!/2,with_level/2,with_level_and_capture/2
Supertester.ETSIsolation
ETSIsolation enables per-test ETS tables, mirroring, and safe injections.
:ok = Supertester.ETSIsolation.setup_ets_isolation()
{:ok, table} = Supertester.ETSIsolation.create_isolated(:set, name: :temp_table)
{:ok, restore} =
Supertester.ETSIsolation.inject_table(MyModule, :table_name, :temp_table)
restore.()Key helpers:
-
setup_ets_isolation/0-2 -
create_isolated/1-2,mirror_table/1-2 -
inject_table/3-4,with_table/2-3
Custom Assertions
Supertester.Assertions
This module provides a set of custom, OTP-aware assertions.
-
assert_process_alive(pid)/assert_process_dead(pid) -
assert_process_restarted(process_name, original_pid) -
assert_genserver_state(server, expected_state_or_fun) -
assert_genserver_responsive(server) -
assert_child_count(supervisor, expected_count) -
assert_all_children_alive(supervisor) -
assert_no_process_leaks(operation_fun) -
assert_memory_usage_stable(operation_fun, tolerance)
Example: assert_genserver_state/2
This assertion can take an exact state or a function to validate the state.
# Exact match
assert_genserver_state(server, %{count: 5})
# Function validation
assert_genserver_state(server, fn state ->
state.count > 0 and state.status == :active
end)Practical Examples & Recipes
Basic GenServer Test
defmodule MyApp.CounterTest do
use ExUnit.Case, async: true
import Supertester.{OTPHelpers, GenServerHelpers, Assertions}
test "counter increments correctly" do
{:ok, counter} = setup_isolated_genserver(Counter)
:ok = cast_and_sync(counter, :increment)
assert_genserver_state(counter, fn s -> s.count == 1 end)
end
endSupervisor Restart Strategy Test
defmodule MyApp.MySupervisorTest do
use ExUnit.Case, async: true
import Supertester.{OTPHelpers, SupervisorHelpers, Assertions}
test "one_for_one strategy restarts only the failed child" do
{:ok, supervisor} = setup_isolated_supervisor(MySupervisor)
result = test_restart_strategy(supervisor, :one_for_one, {:kill_child, :worker_1})
assert result.restarted == [:worker_1]
wait_for_supervisor_stabilization(supervisor)
assert_all_children_alive(supervisor)
end
endChaos Test for System Resilience
defmodule MyApp.ResilienceTest do
use ExUnit.Case, async: true
import Supertester.{OTPHelpers, ChaosHelpers, Assertions}
test "system survives random worker crashes" do
{:ok, supervisor} = setup_isolated_supervisor(WorkerSupervisor)
report = chaos_kill_children(supervisor, kill_rate: 0.5, duration_ms: 2000)
assert Process.alive?(supervisor)
assert_all_children_alive(supervisor)
end
endPerformance SLA Test
defmodule MyApp.PerformanceTest do
use ExUnit.Case, async: true
import Supertester.{OTPHelpers, PerformanceHelpers}
test "API endpoint meets performance SLA" do
{:ok, api_server} = setup_isolated_genserver(APIServer)
assert_performance(
fn -> APIServer.get_user(api_server, 123) end,
max_time_ms: 100,
max_memory_bytes: 1_000_000
)
end
endMemory Leak Detection
defmodule MyApp.MemoryTest do
use ExUnit.Case, async: true
import Supertester.{OTPHelpers, PerformanceHelpers}
test "worker does not leak memory" do
{:ok, worker} = setup_isolated_genserver(Worker)
assert_no_memory_leak(10_000, fn ->
Worker.process(worker, generate_message())
end)
end
endBest Practices
- Always Use Isolation: Start your test modules with
use Supertester.ExUnitFoundation, isolation: :full_isolation. - Prefer
setup_isolated_*: Usesetup_isolated_genserverandsetup_isolated_supervisorto ensure automatic cleanup and unique naming. - Avoid
Process.sleep: Usecast_and_syncfor asynchronous operations andwait_for_*helpers for other synchronization needs. - Use Expressive Assertions: Leverage the custom assertions in
Supertester.Assertionsto make your tests clearer and more concise. - Test for Resilience: Use the
ChaosHelpersto inject faults and ensure your system can handle them gracefully. - Assert Performance: Use
PerformanceHelpersto set performance SLAs and prevent regressions.
Troubleshooting
- Flaky Tests: If your tests are still flaky, ensure every asynchronous operation is followed by a synchronization helper like
cast_and_sync. - Name Conflicts: If you encounter name clashes, make sure you are using
setup_isolated_genserverfor all your processes. - Supervisor Tests Failing: After inducing failures in a supervisor test, always use
wait_for_supervisor_stabilizationbefore making assertions about its children. - Inconsistent Performance Tests: Run
:erlang.garbage_collect()before measuring performance and use a sufficient number of iterations to get stable results.