ZenWebsocket.Testing (ZenWebsocket v0.4.2)

Copy Markdown View Source

Test helpers for consumers testing ZenWebsocket integrations.

This module provides utilities to create mock WebSocket servers, simulate disconnections, inject messages, and assert on client behavior during tests.

Usage

defmodule MyApp.WebSocketTest do
  use ExUnit.Case
  alias ZenWebsocket.Testing

  setup do
    {:ok, server} = Testing.start_mock_server()
    on_exit(fn -> Testing.stop_server(server) end)
    {:ok, server: server}
  end

  test "handles messages", %{server: server} do
    {:ok, client} = ZenWebsocket.Client.connect(server.url)

    # Inject a message from server to client
    Testing.inject_message(server, ~s({"type": "hello"}))

    # Verify client sent expected message
    assert Testing.assert_message_sent(server, ~s({"type": "ping"}), 1000)
  end

  test "handles disconnection", %{server: server} do
    {:ok, client} = ZenWebsocket.Client.connect(server.url)

    # Simulate server disconnect
    Testing.simulate_disconnect(server, :going_away)

    assert ZenWebsocket.Client.get_state(client) == :disconnected
  end
end

Functions

API Functions

FunctionArityDescriptionParam Kinds
assert_message_sent3Assert that a client sent an expected message to the server.server: value, expected: value, timeout_ms: value
inject_message2Inject a message from the server to all connected clients.server: value, message: value
simulate_disconnect2Simulate a WebSocket disconnect from the server side.server: value, reason: value
stop_server1Stop a mock server and clean up all resources.server: value
start_mock_server1Start a mock WebSocket server for testing.opts: value

Summary

Functions

Asserts that a client sent an expected message to the server.

Injects a message from the server to all connected clients.

Simulates a WebSocket disconnect from the server side.

Starts a mock WebSocket server for testing.

Stops a mock server and cleans up all resources.

Types

disconnect_reason()

@type disconnect_reason() :: :normal | :going_away | {:code, pos_integer()}

server()

@type server() :: %{
  pid: pid(),
  port: pos_integer(),
  url: String.t(),
  message_agent: pid()
}

Functions

assert_message_sent(server, expected, timeout_ms)

@spec assert_message_sent(server(), term(), pos_integer()) :: boolean()

Asserts that a client sent an expected message to the server.

This function polls the captured messages and checks if any match the expected pattern within the given timeout.

Pattern Matching

The expected parameter can be:

  • A string for exact match
  • A regex for pattern match
  • A map for partial JSON match (decoded message must contain all keys/values)
  • A function that returns true/false

Examples

# Exact string match
assert Testing.assert_message_sent(server, ~s({"type": "ping"}), 1000)

# Regex match
assert Testing.assert_message_sent(server, ~r/"type": *"ping"/, 1000)

# Partial map match (message must contain these keys)
assert Testing.assert_message_sent(server, %{"type" => "ping"}, 1000)

# Custom function
assert Testing.assert_message_sent(server, fn msg ->
  case Jason.decode(msg) do
    {:ok, %{"type" => "ping"}} -> true
    _ -> false
  end
end, 1000)

inject_message(server, message)

@spec inject_message(server(), binary()) :: :ok

Injects a message from the server to all connected clients.

The message is sent as a text frame to all currently connected WebSocket clients.

Examples

# Send JSON message
Testing.inject_message(server, ~s({"type": "notification", "data": "hello"}))

# Send plain text
Testing.inject_message(server, "ping")

simulate_disconnect(server, reason)

@spec simulate_disconnect(server(), disconnect_reason()) :: :ok

Simulates a WebSocket disconnect from the server side.

This is useful for testing client reconnection behavior and error handling.

Disconnect Reasons

  • :normal - Clean close (code 1000)
  • :going_away - Server shutting down (code 1001)
  • {:code, n} - Custom close code

Examples

# Normal close
Testing.simulate_disconnect(server, :normal)

# Server going away
Testing.simulate_disconnect(server, :going_away)

# Custom close code
Testing.simulate_disconnect(server, {:code, 1008})

start_mock_server(opts \\ [])

@spec start_mock_server(keyword()) :: {:ok, server()} | {:error, term()}

Starts a mock WebSocket server for testing.

Options

  • :port - Port to listen on (default: 0, which assigns a random available port)
  • :protocol - :http or :tls (default: :http)
  • :handler - Custom frame handler function (default: echo handler)

Returns

{:ok, server} where server is a map containing:

  • :pid - Server process PID
  • :port - Actual port the server is listening on
  • :url - Full WebSocket URL for connecting
  • :message_agent - Agent PID for message capture

Examples

{:ok, server} = Testing.start_mock_server()
{:ok, client} = ZenWebsocket.Client.connect(server.url)

# With custom port
{:ok, server} = Testing.start_mock_server(port: 9999)

# With TLS
{:ok, server} = Testing.start_mock_server(protocol: :tls)

stop_server(server)

@spec stop_server(server()) :: :ok

Stops a mock server and cleans up all resources.

This should be called in test teardown (e.g., on_exit callback).

Examples

setup do
  {:ok, server} = Testing.start_mock_server()
  on_exit(fn -> Testing.stop_server(server) end)
  {:ok, server: server}
end