A battle-hardened OTP testing toolkit with chaos engineering, performance testing, and zero-sleep synchronization for building robust Elixir applications.
Version 0.2.1 - Now with chaos engineering, performance testing, and supervision tree testing!
Are you tired of...
- π« Flaky tests that fail randomly due to race conditions?
- π
GenServer
name clashes when running tests withasync: true
? - π°οΈ Littering your test suite with
Process.sleep/1
and 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: true
without 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/1
for more expressive tests. - β¨ Effortless Setup & Teardown: Start isolated
GenServer
s andSupervisor
s with a single line and trustSupertester
to 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.1", only: :test} ] end
Then, 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 end
Supertester
is organized into several powerful modules, each targeting a specific area of OTP testing.
-
Supertester.UnifiedTestFoundation
: The cornerstone of test isolation. Use it in your test cases to create a sandboxed environment, enabling safe concurrent testing with automatic cleanup of processes and ETS tables. -
Supertester.TestableGenServer
: A simple behavior to make yourGenServer
s more testable. It automatically injects handlers to allow deterministic synchronization in your tests, completely eliminating the need forProcess.sleep/1
. -
Supertester.OTPHelpers
: Provides the essential tools for managing the lifecycle of isolated OTP processes. Functions likesetup_isolated_genserver/3
andsetup_isolated_supervisor/3
are your entry point for starting processes within the isolated test environment. -
Supertester.GenServerHelpers
: Contains specialized functions for interacting withGenServer
s. The star of this module iscast_and_sync/2
, which provides a robust way to test asynchronouscast
operations deterministically. It also includes helpers for stress-testing and crash recovery scenarios. -
Supertester.SupervisorHelpers
: A dedicated toolkit for testing the backbone of OTP applications: supervisors. You can verify restart strategies, validate supervision tree structures, and trace supervision events to ensure your application is truly fault-tolerant. -
Supertester.ChaosHelpers
: Unleash controlled chaos to test your system's resilience. This module allows you to inject faults, kill random processes, and simulate resource exhaustion to ensure your system can withstand turbulent conditions. -
Supertester.PerformanceHelpers
: Integrate performance testing directly into your test suite. Assert that your code meets performance SLAs, detect memory leaks, and prevent performance regressions before they hit production. -
Supertester.Assertions
: A rich set of custom assertions that understand OTP primitives. Go beyond simple equality checks with assertions likeassert_genserver_state/2
,assert_all_children_alive/1
, andassert_no_process_leaks/1
for more expressive and meaningful tests.
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) end
Ensure 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) end
Verify 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
For a comprehensive guide to all features, modules, and functions, please see the full User Manual.
The user manual includes:
- In-depth explanations of every module.
- Detailed function signatures, parameters, and return values.
- Practical code examples and recipes for common testing scenarios.
- Best practices for writing robust tests.
Additional documentation, including the technical design, can be found in the docs/
directory.
- π 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.