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?
- π
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.1", 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 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 yourGenServers 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/3andsetup_isolated_supervisor/3are your entry point for starting processes within the isolated test environment. -
Supertester.GenServerHelpers: Contains specialized functions for interacting withGenServers. The star of this module iscast_and_sync/2, which provides a robust way to test asynchronouscastoperations 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/1for 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)
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}
]
})
endFor 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.