A battle-hardened OTP testing toolkit with chaos engineering, performance testing, and zero-sleep synchronization for building robust Elixir applications.
Version 0.2.0 - Now with chaos engineering, performance testing, and supervision tree testing!
Are you tired of...
- π« Flaky tests that fail randomly due to race conditions?
- π
GenServername clashes when running tests withasync: true? - π°οΈ Littering your test suite with
Process.sleep/1and hoping for the best? - π€·ββοΈ Struggling to test process crashes, restarts, and complex supervision trees?
Writing tests for concurrent systems is hard. Traditional testing methods often lead to fragile, non-deterministic, and slow test suites.
Supertester provides a comprehensive suite of tools to write clean, deterministic, and reliable tests for your OTP applications. It replaces fragile timing hacks with robust synchronization patterns and provides powerful helpers for simulating and asserting complex OTP behaviors.
With Supertester, you can build a test suite that is fast, parallel, and trustworthy.
- β
Rock-Solid Test Isolation: Run all your tests with
async: truewithout fear of process name collisions or state leakage. - π Zero Process.sleep: Complete elimination of timing-based synchronization. Use proper OTP patterns for deterministic testing.
- π€ Powerful OTP Assertions: Go beyond
assert. Useassert_process_restarted/2,assert_genserver_state/2, andassert_all_children_alive/1for more expressive tests. - β¨ Effortless Setup & Teardown: Start isolated
GenServers andSupervisors with a single line and trustSupertesterto handle all the cleanup. - π₯ Chaos Engineering: Test system resilience with controlled fault injection, random process crashes, and resource exhaustion.
- π― Supervision Tree Testing: Verify restart strategies, trace supervision events, and validate tree structures.
- β‘ Performance Testing: Assert performance SLAs, detect memory leaks, and prevent regressions with built-in benchmarking.
- π§ TestableGenServer: Automatic injection of sync handlers for deterministic async operation testing.
Add supertester as a dependency in your mix.exs file. It's only needed for the :test environment.
def deps do [ {:supertester, "~> 0.2.0", only: :test} ] endThen, run mix deps.get to install.
See how Supertester transforms a common, fragile test pattern into a robust, deterministic one.
# test/my_app/counter_test.exs defmodule MyApp.CounterTest do use ExUnit.Case, async: false # <-- Forced to run sequentially test "incrementing the counter" do # Manual setup, prone to name conflicts {:ok, _pid} = start_supervised({Counter, name: Counter}) GenServer.cast(Counter, :increment) Process.sleep(50) # <-- Fragile, timing-dependent guess state = GenServer.call(Counter, :state) assert state.count == 1 end end# test/my_app/counter_test.exs defmodule MyApp.CounterTest do use ExUnit.Case, async: true # <-- Fully parallel! # Import the tools you need import Supertester.OTPHelpers import Supertester.GenServerHelpers import Supertester.Assertions test "incrementing the counter" do # Isolated setup with automatic cleanup, no name clashes {:ok, counter_pid} = setup_isolated_genserver(Counter) # Deterministic sync: ensures the cast is processed before continuing :ok = cast_and_sync(counter_pid, :increment) # Expressive, OTP-aware assertion for checking state assert_genserver_state(counter_pid, fn state -> state.count == 1 end) end endSupertester is organized into several modules, each targeting a specific area of OTP testing.
For setting up and managing isolated OTP processes.
setup_isolated_genserver/3: Starts aGenServerwith a unique name and automatic cleanup.setup_isolated_supervisor/3: Starts aSupervisorwith a unique name and automatic cleanup.wait_for_process_restart/3: Blocks until a supervised process has been terminated and restarted.wait_for_genserver_sync/2: Ensures aGenServeris alive and responsive.
For interacting with and testing GenServers.
cast_and_sync/3: Sends acastand waits for a follow-upcallto confirm it was processed.get_server_state_safely/1: FetchesGenServerstate without crashing if the process is down.test_server_crash_recovery/2: Simulates a process crash and verifies its recovery by the supervisor.concurrent_calls/3: Stress-tests aGenServerwith many concurrent requests.
Custom, OTP-aware assertions for more meaningful tests.
assert_process_alive/1&assert_process_dead/1assert_genserver_state/2: Asserts theGenServer's internal state matches a value or passes a function check.assert_child_count/2: Asserts a supervisor has an exact number of active children.assert_all_children_alive/1: Checks that all children in a supervision tree are running.assert_no_process_leaks/1: Ensures an operation cleans up all the processes it spawns.
Provides advanced, case-level isolation for complex scenarios.
defmodule MyApp.MyAdvancedTest do use ExUnit.Case # Choose an isolation level for the entire test module. # :full_isolation provides sandboxed processes and ETS tables. use Supertester.UnifiedTestFoundation, isolation: :full_isolation test "this test runs in a complete sandbox", context do # `context.isolation_context` holds info about the sandbox. # All processes started via Supertester helpers are tracked and auto-cleaned. {:ok, server} = Supertester.OTPHelpers.setup_isolated_genserver(MyServer) # ... your isolated test logic ... end endSpecialized testing for supervision trees and restart strategies.
test_restart_strategy/3: Verify one_for_one, one_for_all, rest_for_one strategiesassert_supervision_tree_structure/2: Validate supervision tree structuretrace_supervision_events/2: Monitor supervisor eventswait_for_supervisor_stabilization/2: Wait for all children to be ready
Chaos engineering toolkit for resilience testing.
inject_crash/3: Controlled crash injection (immediate, delayed, random)chaos_kill_children/3: Random child killing in supervision treessimulate_resource_exhaustion/2: Process/ETS/memory exhaustion simulationassert_chaos_resilient/3: Verify system recovery from chaosrun_chaos_suite/3: Comprehensive chaos scenario testing
Performance testing and regression detection.
assert_performance/2: Assert time/memory/reduction boundsassert_no_memory_leak/2: Detect memory leaks over iterationsmeasure_operation/1: Measure time, memory, and CPU workassert_mailbox_stable/2: Ensure mailbox doesn't grow unboundedcompare_performance/2: Compare multiple function performances
Automatic sync handler injection for GenServers.
- Automatically adds
__supertester_sync__handler to any GenServer - Enables deterministic testing without
Process.sleep/1 - Works seamlessly with existing GenServer implementations
Test your system's resilience to failures:
test "system survives random process crashes" do {:ok, supervisor} = setup_isolated_supervisor(MyApp.WorkerSupervisor) # Kill 50% of workers over 3 seconds report = chaos_kill_children(supervisor, kill_rate: 0.5, duration_ms: 3000, kill_interval_ms: 200 ) # Verify system recovered assert Process.alive?(supervisor) assert report.supervisor_crashed == false assert_all_children_alive(supervisor) endEnsure your code meets performance SLAs:
test "API response time SLA" do {:ok, api_server} = setup_isolated_genserver(APIServer) assert_performance( fn -> APIServer.handle_request(api_server, :get_user) end, max_time_ms: 50, max_memory_bytes: 500_000, max_reductions: 100_000 ) end test "no memory leak in message processing" do {:ok, worker} = setup_isolated_genserver(MessageWorker) assert_no_memory_leak(10_000, fn -> MessageWorker.process(worker, generate_message()) end) endVerify supervision strategies work correctly:
test "one_for_one restarts only 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] assert :worker_2 in result.not_restarted assert :worker_3 in result.not_restarted end test "supervision tree structure" do {:ok, root} = setup_isolated_supervisor(RootSupervisor) assert_supervision_tree_structure(root, %{ supervisor: RootSupervisor, strategy: :one_for_one, children: [ {:cache, CacheServer}, {:worker_pool, WorkerPoolSupervisor} ] }) end- π Zero Process.sleep: Eliminated all timing-based synchronization
- π ChaosHelpers: Complete chaos engineering toolkit
- π PerformanceHelpers: Performance testing and regression detection
- π SupervisorHelpers: Comprehensive supervision tree testing
- π TestableGenServer: Automatic sync handler injection
- π 37 tests: All passing with 100% async execution
See CHANGELOG.md for detailed changes.
Contributions are welcome! If you'd like to help improve Supertester, please feel free to:
- Fork the repository.
- Create a new feature branch.
- Add your feature or bug fix.
- Ensure all new code is covered by tests.
- Open a pull request.
This project is licensed under the MIT License.