Supertester.OTPHelpers (supertester v0.1.0)

View Source

OTP-compliant testing utilities for GenServer, Supervisor, and process management.

This module provides helpers that replace timing-based synchronization (Process.sleep) with proper OTP synchronization patterns, enabling reliable async testing.

Key Features

  • Unique process naming to prevent conflicts
  • OTP-compliant synchronization without Process.sleep
  • Automatic resource cleanup
  • Process lifecycle management
  • Supervision tree testing utilities

Usage

import Supertester.OTPHelpers

test "my genserver test" do
  {:ok, server} = setup_isolated_genserver(MyGenServer, "test_context")
  assert_genserver_responsive(server)
end

Summary

Functions

Registers a cleanup function to run on test exit.

Cleans up a list of processes safely.

Monitors a process lifecycle and returns monitoring information.

Sets up an isolated GenServer with unique naming and automatic cleanup.

Sets up an isolated Supervisor with unique naming and automatic cleanup.

Waits for a GenServer to be synchronized and responsive.

Waits for a process to terminate.

Waits for a process to restart after termination.

Waits for a supervisor to finish starting all its children.

Functions

cleanup_on_exit(cleanup_fun)

@spec cleanup_on_exit((-> any())) :: :ok

Registers a cleanup function to run on test exit.

Parameters

  • cleanup_fun - Function to call on test exit

Example

cleanup_on_exit(fn -> GenServer.stop(my_server) end)

cleanup_processes(pids)

@spec cleanup_processes([pid()]) :: :ok

Cleans up a list of processes safely.

Parameters

  • pids - List of PIDs to clean up

Example

cleanup_processes([pid1, pid2, pid3])

monitor_process_lifecycle(pid)

@spec monitor_process_lifecycle(pid()) :: {reference(), pid()}

Monitors a process lifecycle and returns monitoring information.

Parameters

  • pid - The process to monitor

Returns

{monitor_ref, pid}

Example

{ref, pid} = monitor_process_lifecycle(server_pid)

setup_isolated_genserver(module, test_name \\ "", opts \\ [])

@spec setup_isolated_genserver(module(), String.t(), keyword()) ::
  {:ok, pid()} | {:error, term()}

Sets up an isolated GenServer with unique naming and automatic cleanup.

Parameters

  • module - The GenServer module to start
  • test_name - Test context for unique naming (optional)
  • opts - Options passed to GenServer.start_link (optional)

Returns

{:ok, server_pid} or {:error, reason}

Example

{:ok, server} = setup_isolated_genserver(MyGenServer, "my_test", [initial_state: %{}])

setup_isolated_supervisor(module, test_name \\ "", opts \\ [])

@spec setup_isolated_supervisor(module(), String.t(), keyword()) ::
  {:ok, pid()} | {:error, term()}

Sets up an isolated Supervisor with unique naming and automatic cleanup.

Parameters

  • module - The Supervisor module to start
  • test_name - Test context for unique naming (optional)
  • opts - Options passed to Supervisor.start_link (optional)

Returns

{:ok, supervisor_pid} or {:error, reason}

Example

{:ok, supervisor} = setup_isolated_supervisor(MySupervisor, "my_test")

wait_for_genserver_sync(server, timeout \\ 1000)

@spec wait_for_genserver_sync(GenServer.server(), timeout()) :: :ok | {:error, term()}

Waits for a GenServer to be synchronized and responsive.

Uses GenServer.call with a sync message instead of Process.sleep.

Parameters

  • server - The GenServer pid or name
  • timeout - Timeout in milliseconds (default: 1000)

Returns

:ok if server is responsive, {:error, reason} otherwise

Example

wait_for_genserver_sync(server, 5000)

wait_for_process_death(pid, timeout \\ 1000)

@spec wait_for_process_death(pid(), timeout()) :: {:ok, term()} | {:error, :timeout}

Waits for a process to terminate.

Parameters

  • pid - The process to wait for
  • timeout - Timeout in milliseconds (default: 1000)

Returns

{:ok, reason} if process died, {:error, :timeout} if timeout

Example

ref = Process.monitor(pid)
Process.exit(pid, :kill)
{:ok, :killed} = wait_for_process_death(pid)

wait_for_process_restart(process_name, original_pid, timeout \\ 1000)

@spec wait_for_process_restart(atom(), pid(), timeout()) ::
  {:ok, pid()} | {:error, term()}

Waits for a process to restart after termination.

Parameters

  • process_name - The registered name of the process
  • original_pid - The PID before restart
  • timeout - Timeout in milliseconds (default: 1000)

Returns

{:ok, new_pid} if process restarted, {:error, reason} otherwise

Example

original_pid = GenServer.whereis(MyServer)
GenServer.stop(MyServer)
{:ok, new_pid} = wait_for_process_restart(MyServer, original_pid)

wait_for_supervisor_restart(supervisor, timeout \\ 1000)

@spec wait_for_supervisor_restart(Supervisor.supervisor(), timeout()) ::
  {:ok, pid()} | {:error, term()}

Waits for a supervisor to finish starting all its children.

Parameters

  • supervisor - The supervisor pid or name
  • timeout - Timeout in milliseconds (default: 1000)

Returns

:ok if supervisor is ready, {:error, reason} otherwise

Example

{:ok, supervisor} = setup_isolated_supervisor(MySupervisor)
wait_for_supervisor_restart(supervisor)