ExMCP.Testing.Assertions (ex_mcp v0.10.0)

View Source

Custom assertions for MCP protocol testing.

This module provides MCP-specific assertions that go beyond basic ExUnit assertions, offering detailed validation for protocol compliance, content structure, and common MCP patterns.

Features

  • Protocol Assertions: Validate MCP message structure and compliance
  • Content Assertions: Type-safe content validation and inspection
  • Tool Assertions: Validate tool definitions and results
  • Resource Assertions: Validate resource definitions and responses
  • Error Assertions: Structured error validation
  • Performance Assertions: Response time and throughput validation

Usage

use ExMCP.TestCase

test "tool call returns valid content" do
  result = call_tool(client, "sample_tool", %{input: "test"})

  assert_success(result)
  assert_valid_tool_result(result)
  assert_content_type(result, :text)
  assert_content_contains(result, "expected output")
end

Summary

Types

Assertion options for customizing behavior

MCP message types for assertion validation

Functions

Asserts that all items in a list pass a validation function.

Asserts that text content contains a specific string.

Asserts that content matches a regular expression.

Asserts that content is of a specific type.

Asserts that a response indicates an error.

Asserts that a value becomes true within a timeout period.

Asserts that a list of resources contains a specific resource.

Asserts that a list of tools contains a specific tool.

Asserts that a value matches MCP message structure.

Asserts that an operation completes within a time limit.

Asserts that events occur in a specific order.

Asserts that a response indicates success.

Asserts that multiple operations maintain average performance.

Asserts that content is valid according to MCP content protocol.

Asserts that a prompt definition is valid.

Asserts that a resource definition is valid.

Asserts that a tool definition is valid.

Asserts that a tool result contains valid content.

Types

assertion_opts()

@type assertion_opts() :: [
  strict: boolean(),
  timeout: pos_integer(),
  retry_count: pos_integer(),
  message: String.t()
]

Assertion options for customizing behavior

mcp_message_type()

@type mcp_message_type() ::
  :request
  | :response
  | :notification
  | :error
  | :initialize
  | :list_tools
  | :call_tool
  | :list_resources
  | :read_resource
  | :list_prompts
  | :get_prompt

MCP message types for assertion validation

Functions

assert_all_valid(items, validator)

@spec assert_all_valid([any()], (any() -> any())) :: [any()]

Asserts that all items in a list pass a validation function.

Examples

assert_all_valid(tools, &assert_valid_tool/1)
assert_all_valid(content_list, fn content ->
  assert_content_type(content, :text)
  assert_content_contains(content, "required")
end)

assert_content_contains(map, expected)

@spec assert_content_contains(ExMCP.Content.Protocol.content() | map(), String.t()) ::
  any()

Asserts that text content contains a specific string.

Examples

assert_content_contains(text_content, "hello")
assert_content_contains(tool_result, "expected output")

assert_content_matches(map, pattern)

@spec assert_content_matches(ExMCP.Content.Protocol.content() | map(), Regex.t()) ::
  any()

Asserts that content matches a regular expression.

Examples

assert_content_matches(content, ~r/hello \w+/)
assert_content_matches(tool_result, ~r/\d{4}-\d{2}-\d{2}/)

assert_content_type(content_list, expected_type)

@spec assert_content_type(ExMCP.Content.Protocol.content() | map() | [map()], atom()) ::
  any()

Asserts that content is of a specific type.

Examples

assert_content_type(content, :text)
assert_content_type(result, :image)
assert_content_type(result["content"], :text)  # For tool results

assert_error(result, validator \\ nil, message \\ nil)

@spec assert_error(any(), (any() -> any()) | nil, String.t() | nil) :: any()

Asserts that a response indicates an error.

Examples

assert_error({:error, :timeout})
assert_error(%{"error" => %{"code" => -1}})

# With specific error checking
assert_error(result, fn error ->
  assert error.code == -32601
  assert error.message =~ "Method not found"
end)

assert_eventually(condition, opts \\ [])

@spec assert_eventually((-> boolean()), assertion_opts()) :: :ok

Asserts that a value becomes true within a timeout period.

Examples

assert_eventually fn ->
  Process.alive?(pid)
end, timeout: 1000

assert_eventually fn ->
  GenServer.call(server, :is_ready)
end, timeout: 5000, interval: 100

assert_has_resource(resources, resource_uri)

@spec assert_has_resource([map()], String.t()) :: map()

Asserts that a list of resources contains a specific resource.

Examples

resources = [%{"uri" => "file://a.txt"}, %{"uri" => "file://b.txt"}]
assert_has_resource(resources, "file://a.txt")

assert_has_tool(tools, tool_name)

@spec assert_has_tool([map()], String.t()) :: map()

Asserts that a list of tools contains a specific tool.

Examples

tools = [%{"name" => "tool1"}, %{"name" => "tool2"}]
assert_has_tool(tools, "tool1")

assert_mcp_message(message, type, opts \\ [])

@spec assert_mcp_message(map(), mcp_message_type(), keyword()) :: map()

Asserts that a value matches MCP message structure.

Examples

assert_mcp_message(message, :request)
assert_mcp_message(message, :response, id: 123)
assert_mcp_message(message, :notification, method: "notifications/message")

assert_performance(operation, opts \\ [])

@spec assert_performance((-> any()), assertion_opts()) :: any()

Asserts that an operation completes within a time limit.

Examples

assert_performance fn ->
  slow_operation()
end, max_time: 1000

assert_performance fn ->
  call_tool(client, "tool", %{})
end, max_time: 500, message: "Tool call should be fast"

assert_sequence(operations, list)

(macro)

Asserts that events occur in a specific order.

Examples

assert_sequence([
  fn -> send(self(), :event1) end,
  fn -> send(self(), :event2) end,
  fn -> send(self(), :event3) end
]) do
  assert_received :event1
  assert_received :event2
  assert_received :event3
end

assert_success(result, message \\ nil)

@spec assert_success(any(), String.t() | nil) :: any()

Asserts that a response indicates success.

Examples

assert_success({:ok, result})
assert_success(%{"status" => "success"})

# With custom message
assert_success(result, "Tool call should succeed")

assert_throughput(operation, opts \\ [])

@spec assert_throughput((-> any()), assertion_opts()) :: [any()]

Asserts that multiple operations maintain average performance.

Examples

assert_throughput fn ->
  call_tool(client, "tool", %{})
end, iterations: 10, max_avg_time: 100

assert_valid_content(content)

@spec assert_valid_content(ExMCP.Content.Protocol.content() | map()) ::
  ExMCP.Content.Protocol.content() | map()

Asserts that content is valid according to MCP content protocol.

Examples

assert_valid_content(Protocol.text("Hello"))
assert_valid_content(%{"type" => "text", "text" => "Hello"})

assert_valid_prompt(prompt)

@spec assert_valid_prompt(map()) :: map()

Asserts that a prompt definition is valid.

Examples

prompt = %{
  "name" => "sample_prompt",
  "description" => "A sample prompt",
  "arguments" => [
    %{"name" => "topic", "description" => "The topic", "required" => true}
  ]
}
assert_valid_prompt(prompt)

assert_valid_resource(resource)

@spec assert_valid_resource(map()) :: map()

Asserts that a resource definition is valid.

Examples

resource = %{
  "uri" => "file://data.txt",
  "name" => "Sample Data",
  "description" => "Sample data file",
  "mimeType" => "text/plain"
}
assert_valid_resource(resource)

assert_valid_tool(tool)

@spec assert_valid_tool(map()) :: map()

Asserts that a tool definition is valid.

Examples

tool = %{
  "name" => "sample_tool",
  "description" => "A sample tool",
  "inputSchema" => %{"type" => "object"}
}
assert_valid_tool(tool)

assert_valid_tool_result(result)

@spec assert_valid_tool_result(map()) :: map()

Asserts that a tool result contains valid content.

Examples

result = %{"content" => [%{"type" => "text", "text" => "Hello"}]}
assert_valid_tool_result(result)