Skip to content

Testing ActiveAgent Applications ​

This guide covers testing strategies and utilities for ActiveAgent applications, including credential management, VCR integration, and test patterns.

Credential Management ​

ActiveAgent provides helper methods for checking provider credentials in tests. These helpers check both Rails credentials and environment variables.

Available Helper Methods ​

All test classes that inherit from ActiveSupport::TestCase have access to these credential helpers:

ruby
# Check if any provider has credentials
has_provider_credentials?(provider)  # :openai, :anthropic, :open_router, :ollama

# Provider-specific helpers
has_openai_credentials?       # Checks Rails credentials and ENV vars
has_anthropic_credentials?    # Checks Rails credentials and ENV vars
has_openrouter_credentials?   # Checks Rails credentials and ENV vars
has_ollama_credentials?       # Checks for Ollama server configuration

Using Credential Helpers in Tests ​

Skip tests when credentials aren't available:

ruby
class MyAgentTest < ActiveSupport::TestCase
  test "generates response with OpenAI" do
    skip "Requires API credentials" unless has_openai_credentials?
    
    # Test implementation
  end
  
  test "uses Anthropic for complex reasoning" do
    skip "Requires API credentials" unless has_anthropic_credentials?
    
    # Test implementation
  end
end

Credential Configuration ​

Credentials are checked in this order:

  1. Rails Credentials (Recommended)

    bash
    rails credentials:edit
    yaml
    openai:
      access_token: your-api-key
    anthropic:
      access_token: your-api-key
    open_router:
      access_token: your-api-key  # or api_key
  2. Environment Variables (Fallback)

    bash
    export OPENAI_ACCESS_TOKEN=your-api-key
    export OPENAI_API_KEY=your-api-key  # Alternative
    export ANTHROPIC_ACCESS_TOKEN=your-api-key
    export ANTHROPIC_API_KEY=your-api-key  # Alternative
    export OPENROUTER_API_KEY=your-api-key

Testing Agent Actions ​

The Correct Pattern ​

ActiveAgent uses a specific pattern for calling agent actions with parameters:

ruby
# CORRECT: Use the class method 'with' to pass parameters
generation = MyAgent.with(param1: "value1", param2: "value2").action_name
result = generation.generate_now

# INCORRECT: Don't call actions directly with arguments
# agent = MyAgent.new
# result = agent.action_name(param1: "value1")  # This won't work!

Complete Test Example ​

ruby
test "web search with responses API example" do
  skip "Requires API credentials" unless has_openai_credentials?

  VCR.use_cassette("doc_web_search_responses") do
    generation = WebSearchAgent.with(
      query: "Latest Ruby on Rails 8 features",
      context_size: "high"
    ).search_with_tools

    result = generation.generate_now

    # The response includes web search results
    assert result.message.content.present?
    assert result.message.content.include?("Rails")

    doc_example_output(result)
  end
end

Testing with VCR ​

Use VCR to record and replay API responses:

ruby
class MyAgentTest < ActiveSupport::TestCase
  test "performs complex task" do
    skip "Requires API credentials" unless has_openai_credentials?
    
    VCR.use_cassette("my_agent_complex_task") do
      generation = MyAgent.with(
        input: "test data",
        mode: "analysis"
      ).analyze
      
      result = generation.generate_now
      
      assert result.message.content.present?
      assert result.message.content.include?("expected text")
      
      # Generate documentation examples
      doc_example_output(result)
    end
  end
end

Testing Concerns ​

ActiveAgent supports using concerns to share functionality across agents:

Creating a Test for a Concern ​

ruby
require "test_helper"
require_relative "../dummy/app/agents/research_agent"
require_relative "../dummy/app/agents/concerns/research_tools"

class ConcernToolsTest < ActiveSupport::TestCase
  setup do
    @agent = ResearchAgent.new
  end

  test "research agent includes concern actions as available tools" do
    # The concern adds these actions which should be available as tools
    expected_actions = [
      "search_academic_papers",
      "analyze_research_data",
      "generate_research_visualization",
      "search_with_mcp_sources"
    ]

    agent_actions = @agent.action_methods
    expected_actions.each do |action|
      assert_includes agent_actions, action, "Expected #{action} to be available from concern"
    end
  end

  test "concern can add built-in tools for responses API" do
    skip "Requires API credentials" unless has_openai_credentials?

    VCR.use_cassette("concern_web_search_responses_api") do
      # When using responses API with multimodal content
      # Use the search_academic_papers action from the concern
      generation = ResearchAgent.with(
        query: "latest research on large language models",
        year_from: 2024,
        year_to: 2025,
        field: "AI"
      ).search_academic_papers

      response = generation.generate_now

      assert response.message.content.present?
    end
  end

  test "concern can configure web search for chat completions API" do
    skip "Requires API credentials" unless has_openai_credentials?

    VCR.use_cassette("concern_web_search_chat_api") do
      # When using chat API with web search model
      # Use the comprehensive_research action which builds tools dynamically
      generation = ResearchAgent.with(
        topic: "latest research on large language models",
        depth: "detailed"
      ).comprehensive_research

      response = generation.generate_now

      assert response.message.content.present?
    end
  end

  test "concern supports MCP tools only in responses API" do
    skip "Requires API credentials" unless has_openai_credentials?

    VCR.use_cassette("concern_mcp_tools") do
      # MCP is only supported in Responses API
      # Use the search_with_mcp_sources action from the concern
      generation = ResearchAgent.with(
        query: "Ruby on Rails best practices",
        sources: [ "github" ]
      ).search_with_mcp_sources

      response = generation.generate_now

      assert response.message.content.present?
    end
  end

  test "concern actions work with both chat and responses API" do
    # Test that the same action can work with different APIs

    # Test with Chat Completions API (function calling)
    chat_prompt = ActiveAgent::ActionPrompt::Prompt.new
    chat_prompt.options = { model: "gpt-4o" }
    chat_prompt.actions = @agent.action_schemas  # Function schemas

    assert chat_prompt.actions.any? { |a| a["function"]["name"] == "search_academic_papers" }

    # Test with Responses API (can use built-in tools)
    responses_prompt = ActiveAgent::ActionPrompt::Prompt.new
    responses_prompt.options = {
      model: "gpt-5",
      use_responses_api: true,
      tools: [
        { type: "web_search_preview" },
        { type: "image_generation" }
      ]
    }

    # Should have both function tools and built-in tools
    assert responses_prompt.options[:tools].any? { |t| t[:type] == "web_search_preview" }
    assert responses_prompt.options[:tools].any? { |t| t[:type] == "image_generation" }
  end

  test "concern can dynamically configure tools based on context" do
    # The concern can decide which tools to include based on parameters

    # The ResearchAgent.comprehensive_research method should configure tools dynamically
    # based on the depth parameter. We verify this by checking that the agent
    # has the method and that it accepts the expected parameters.

    agent = ResearchAgent.new
    assert agent.respond_to?(:comprehensive_research)

    # Verify the agent has access to the tool configuration methods
    assert ResearchAgent.research_tools_config[:enable_web_search]
    assert ResearchAgent.research_tools_config[:mcp_servers].present?
  end

  test "concern configuration is inherited at class level" do
    # ResearchAgent configured with specific settings
    assert ResearchAgent.research_tools_config[:enable_web_search]
    assert_equal [ "arxiv", "github" ], ResearchAgent.research_tools_config[:mcp_servers]
    assert_equal "high", ResearchAgent.research_tools_config[:default_search_context]
  end

  test "multiple concerns can add different tool types" do
    # Create an agent with multiple concerns
    class MultiToolAgent < ApplicationAgent
      include ResearchTools
      # Could include other tool concerns like ImageTools, DataTools, etc.

      generate_with :openai, model: "gpt-4o"
    end

    agent = MultiToolAgent.new

    # Should have all actions from all concerns
    assert agent.respond_to?(:search_academic_papers)
    assert agent.respond_to?(:analyze_research_data)
    assert agent.respond_to?(:generate_research_visualization)
  end

  test "concern tools respect API limitations" do
    # Test that we don't try to use unsupported features

    # MCP should not be available in Chat API
    chat_prompt = ActiveAgent::ActionPrompt::Prompt.new
    chat_prompt.options = {
      model: "gpt-4o",  # Regular chat model
      tools: [
        { type: "mcp", server_url: "https://example.com" }  # This won't work
      ]
    }

    # Provider should filter out MCP for chat API
    provider = ActiveAgent::GenerationProvider::OpenAIProvider.new({ "model" => "gpt-4o" })
    provider.instance_variable_set(:@prompt, chat_prompt)

    # When using chat API, MCP tools should not be included
    # This test verifies that the configuration is set up correctly
    assert chat_prompt.options[:model] == "gpt-4o"
    assert chat_prompt.options[:tools].any? { |t| t[:type] == "mcp" }
  end
end

Testing Concern Configuration ​

ruby
require "test_helper"
require_relative "../dummy/app/agents/research_agent"
require_relative "../dummy/app/agents/concerns/research_tools"

class ConcernToolsTest < ActiveSupport::TestCase
  setup do
    @agent = ResearchAgent.new
  end

  test "research agent includes concern actions as available tools" do
    # The concern adds these actions which should be available as tools
    expected_actions = [
      "search_academic_papers",
      "analyze_research_data",
      "generate_research_visualization",
      "search_with_mcp_sources"
    ]

    agent_actions = @agent.action_methods
    expected_actions.each do |action|
      assert_includes agent_actions, action, "Expected #{action} to be available from concern"
    end
  end

  test "concern can add built-in tools for responses API" do
    skip "Requires API credentials" unless has_openai_credentials?

    VCR.use_cassette("concern_web_search_responses_api") do
      # When using responses API with multimodal content
      # Use the search_academic_papers action from the concern
      generation = ResearchAgent.with(
        query: "latest research on large language models",
        year_from: 2024,
        year_to: 2025,
        field: "AI"
      ).search_academic_papers

      response = generation.generate_now

      assert response.message.content.present?
    end
  end

  test "concern can configure web search for chat completions API" do
    skip "Requires API credentials" unless has_openai_credentials?

    VCR.use_cassette("concern_web_search_chat_api") do
      # When using chat API with web search model
      # Use the comprehensive_research action which builds tools dynamically
      generation = ResearchAgent.with(
        topic: "latest research on large language models",
        depth: "detailed"
      ).comprehensive_research

      response = generation.generate_now

      assert response.message.content.present?
    end
  end

  test "concern supports MCP tools only in responses API" do
    skip "Requires API credentials" unless has_openai_credentials?

    VCR.use_cassette("concern_mcp_tools") do
      # MCP is only supported in Responses API
      # Use the search_with_mcp_sources action from the concern
      generation = ResearchAgent.with(
        query: "Ruby on Rails best practices",
        sources: [ "github" ]
      ).search_with_mcp_sources

      response = generation.generate_now

      assert response.message.content.present?
    end
  end

  test "concern actions work with both chat and responses API" do
    # Test that the same action can work with different APIs

    # Test with Chat Completions API (function calling)
    chat_prompt = ActiveAgent::ActionPrompt::Prompt.new
    chat_prompt.options = { model: "gpt-4o" }
    chat_prompt.actions = @agent.action_schemas  # Function schemas

    assert chat_prompt.actions.any? { |a| a["function"]["name"] == "search_academic_papers" }

    # Test with Responses API (can use built-in tools)
    responses_prompt = ActiveAgent::ActionPrompt::Prompt.new
    responses_prompt.options = {
      model: "gpt-5",
      use_responses_api: true,
      tools: [
        { type: "web_search_preview" },
        { type: "image_generation" }
      ]
    }

    # Should have both function tools and built-in tools
    assert responses_prompt.options[:tools].any? { |t| t[:type] == "web_search_preview" }
    assert responses_prompt.options[:tools].any? { |t| t[:type] == "image_generation" }
  end

  test "concern can dynamically configure tools based on context" do
    # The concern can decide which tools to include based on parameters

    # The ResearchAgent.comprehensive_research method should configure tools dynamically
    # based on the depth parameter. We verify this by checking that the agent
    # has the method and that it accepts the expected parameters.

    agent = ResearchAgent.new
    assert agent.respond_to?(:comprehensive_research)

    # Verify the agent has access to the tool configuration methods
    assert ResearchAgent.research_tools_config[:enable_web_search]
    assert ResearchAgent.research_tools_config[:mcp_servers].present?
  end

  test "concern configuration is inherited at class level" do
    # ResearchAgent configured with specific settings
    assert ResearchAgent.research_tools_config[:enable_web_search]
    assert_equal [ "arxiv", "github" ], ResearchAgent.research_tools_config[:mcp_servers]
    assert_equal "high", ResearchAgent.research_tools_config[:default_search_context]
  end

  test "multiple concerns can add different tool types" do
    # Create an agent with multiple concerns
    class MultiToolAgent < ApplicationAgent
      include ResearchTools
      # Could include other tool concerns like ImageTools, DataTools, etc.

      generate_with :openai, model: "gpt-4o"
    end

    agent = MultiToolAgent.new

    # Should have all actions from all concerns
    assert agent.respond_to?(:search_academic_papers)
    assert agent.respond_to?(:analyze_research_data)
    assert agent.respond_to?(:generate_research_visualization)
  end

  test "concern tools respect API limitations" do
    # Test that we don't try to use unsupported features

    # MCP should not be available in Chat API
    chat_prompt = ActiveAgent::ActionPrompt::Prompt.new
    chat_prompt.options = {
      model: "gpt-4o",  # Regular chat model
      tools: [
        { type: "mcp", server_url: "https://example.com" }  # This won't work
      ]
    }

    # Provider should filter out MCP for chat API
    provider = ActiveAgent::GenerationProvider::OpenAIProvider.new({ "model" => "gpt-4o" })
    provider.instance_variable_set(:@prompt, chat_prompt)

    # When using chat API, MCP tools should not be included
    # This test verifies that the configuration is set up correctly
    assert chat_prompt.options[:model] == "gpt-4o"
    assert chat_prompt.options[:tools].any? { |t| t[:type] == "mcp" }
  end
end

Generating Documentation Examples ​

Use the doc_example_output method to generate documentation examples from test results:

ruby
test "example for documentation" do
  VCR.use_cassette("doc_example") do
    generation = MyAgent.with(
      query: "How do I use ActiveAgent?"
    ).help
    
    result = generation.generate_now
    
    # Generate example file in docs/parts/examples/
    doc_example_output(result)
  end
end

The generated examples can be included in documentation:

markdown
::: details Response Example
<!-- @include: @/parts/examples/my-agent-test-example-for-documentation.md -->
:::

Test Helpers ​

ActiveAgentTestCase ​

Use ActiveAgentTestCase for tests that need to manage ActiveAgent configuration:

ruby
class ConfigurationTest < ActiveAgentTestCase
  def test_with_custom_config
    with_active_agent_config(custom_config) do
      # Test with custom configuration
    end
  end
end

Testing Multiple Providers ​

Test agent behavior across different providers:

ruby
class MultiProviderTest < ActiveSupport::TestCase
  test "works with OpenAI" do
    skip unless has_openai_credentials?
    
    agent_class = Class.new(ApplicationAgent) do
      generate_with :openai, model: "gpt-4o"
      
      def test_action
        prompt message: "test"
      end
    end
    
    generation = agent_class.with.test_action
    result = generation.generate_now
    assert result.message.content.present?
  end
  
  test "works with Anthropic" do
    skip unless has_anthropic_credentials?
    
    agent_class = Class.new(ApplicationAgent) do
      generate_with :anthropic, model: "claude-3-5-sonnet-latest"
      
      def test_action
        prompt message: "test"
      end
    end
    
    generation = agent_class.with.test_action
    result = generation.generate_now
    assert result.message.content.present?
  end
end

Testing Built-in Tools ​

When testing agents that use OpenAI's built-in tools (web search, image generation, MCP):

Web Search Testing ​

ruby
test "searches the web for information" do
  skip unless has_openai_credentials?
  
  VCR.use_cassette("web_search_test") do
    generation = WebSearchAgent.with(
      query: "Latest Ruby on Rails features",
      context_size: "high"
    ).search_with_tools
    
    result = generation.generate_now
    assert result.message.content.present?
  end
end

Image Generation Testing ​

ruby
test "generates images" do
  skip unless has_openai_credentials?
  
  VCR.use_cassette("image_generation_test") do
    generation = MultimodalAgent.with(
      description: "A peaceful mountain landscape",
      size: "1024x1024",
      quality: "high"
    ).create_image
    
    result = generation.generate_now
    assert result.message.content.present?
  end
end

MCP Integration Testing ​

ruby
test "connects to MCP servers" do
  skip unless has_openai_credentials?
  
  VCR.use_cassette("mcp_integration_test") do
    generation = ResearchAgent.with(
      query: "Ruby performance optimization",
      sources: ["github"]
    ).search_with_mcp_sources
    
    result = generation.generate_now
    assert result.message.content.present?
  end
end

Best Practices ​

  1. Always use credential helpers - Skip tests gracefully when credentials aren't available
  2. Use VCR for API calls - Record responses for consistent, fast tests
  3. Follow the ActiveAgent pattern - Use Agent.with(params).action.generate_now
  4. Generate documentation examples - Use doc_example_output for real examples
  5. Test concerns separately - Ensure shared functionality works correctly
  6. Clean up VCR cassettes - Remove old cassettes when updating test implementations
  7. Test error handling - Ensure agents handle API errors gracefully

Common Issues ​

Wrong Pattern Errors ​

If you see wrong number of arguments errors, you're likely calling actions incorrectly:

ruby
# WRONG - Actions don't accept arguments directly
agent = MyAgent.new
agent.my_action(param: "value")  # Error!

# RIGHT - Use the with pattern
MyAgent.with(param: "value").my_action.generate_now

Missing Credentials ​

If tests are skipped unexpectedly, check:

  1. Rails credentials are properly set: rails credentials:edit
  2. Environment variables are exported in your shell
  3. The credential helper is checking the right keys

VCR Cassette Issues ​

If tests fail with API errors when cassettes exist:

  1. Delete old cassettes: rm test/fixtures/vcr_cassettes/your_test.yml
  2. Re-run tests to record new cassettes
  3. Ensure API credentials are valid