Skip to main content
Goose has a comprehensive test suite covering unit tests, integration tests, and MCP extension tests. This guide explains how to write and run tests.

Running Tests

All Tests

Run the entire test suite:
cargo test

Specific Crate

Test a single crate:
cargo test -p goose
cargo test -p goose-server
cargo test -p goose-mcp

Specific Test

Run a specific test by name:
cargo test test_provider_stream
cargo test --package goose --test mcp_integration_test

MCP Integration Tests

Run MCP tests with replay recording:
just record-mcp-tests
This runs tests and records MCP interactions to crates/goose/tests/mcp_replays/.

UI Tests

Test the desktop UI:
cd ui/desktop
npm test

Test Organization

Unit Tests

Unit tests live alongside the code they test:
// In crates/goose/src/providers/base.rs
#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_usage_creation() {
        let usage = Usage::new(Some(10), Some(20), Some(30));
        assert_eq!(usage.input_tokens, Some(10));
        assert_eq!(usage.output_tokens, Some(20));
        assert_eq!(usage.total_tokens, Some(30));
    }
}

Integration Tests

Integration tests are in dedicated tests/ directories:
crates/goose/tests/
├── agent.rs
├── mcp_integration_test.rs
├── providers.rs
└── mcp_replays/
Integration tests can import from the crate:
use goose::agents::Agent;
use goose::providers::base::Provider;

Writing Tests

Basic Test Structure

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_basic_functionality() {
        // Arrange
        let input = "test";
        
        // Act
        let result = process(input);
        
        // Assert
        assert_eq!(result, "expected");
    }
}

Async Tests

Use #[tokio::test] for async tests:
#[tokio::test]
async fn test_async_function() {
    let result = async_operation().await;
    assert!(result.is_ok());
}

Test Case Macro

Use test_case for parameterized tests:
use test_case::test_case;

#[test_case("hello", "HELLO" ; "lowercase")]
#[test_case("WORLD", "WORLD" ; "already uppercase")]
fn test_uppercase(input: &str, expected: &str) {
    assert_eq!(input.to_uppercase(), expected);
}

Testing Providers

Create a mock provider for testing:
use goose::providers::base::{Provider, ProviderDef};
use async_trait::async_trait;

#[derive(Clone)]
pub struct MockProvider {
    model_config: ModelConfig,
}

#[async_trait]
impl Provider for MockProvider {
    fn get_name(&self) -> &str {
        "mock"
    }
    
    async fn stream(
        &self,
        _model_config: &ModelConfig,
        _session_id: &str,
        _system: &str,
        _messages: &[Message],
        _tools: &[Tool],
    ) -> Result<MessageStream, ProviderError> {
        let message = Message::assistant()
            .with_text("Test response");
        let usage = ProviderUsage::new(
            "mock".to_string(),
            Usage::default()
        );
        Ok(stream_from_single_message(message, usage))
    }
    
    fn get_model_config(&self) -> ModelConfig {
        self.model_config.clone()
    }
}

MCP Integration Testing

MCP tests verify extension behavior (crates/goose/tests/mcp_integration_test.rs).

Test Structure

#[tokio::test]
async fn test_mcp_tool() {
    // Create extension manager
    let manager = ExtensionManager::new(
        vec![extension_config],
        cancellation_token,
    ).await.unwrap();
    
    // List available tools
    let tools = manager.list_tools().await.unwrap();
    assert!(!tools.is_empty());
    
    // Call a tool
    let result = manager.call_tool(
        "tool_name",
        tool_params,
    ).await.unwrap();
    
    assert!(result.is_success());
}

Recording MCP Interactions

Set GOOSE_RECORD_MCP=1 to record interactions:
GOOSE_RECORD_MCP=1 cargo test --package goose --test mcp_integration_test
Recorded interactions are saved to crates/goose/tests/mcp_replays/ and can be used for regression testing.

Test Utilities

goose-test Crate

Provides utilities for testing:
use goose_test::test_helpers::*;

// Create test session
let session = create_test_session();

// Create test message
let message = test_message("Hello");

goose-test-support Crate

Helpers for integration tests:
use goose_test_support::*;

// Setup test environment
let test_env = TestEnvironment::new();

Testing Extensions

Extension Test Pattern

use goose::agents::extension::ExtensionConfig;
use goose_mcp::MemoryServer;

#[tokio::test]
async fn test_memory_extension() {
    // Create extension config
    let config = ExtensionConfig::builtin("memory");
    
    // Create extension manager
    let manager = ExtensionManager::new(
        vec![config],
        CancellationToken::new(),
    ).await.unwrap();
    
    // Test tool listing
    let tools = manager.list_tools().await.unwrap();
    let has_memory_tool = tools.iter()
        .any(|t| t.name == "store_memory");
    assert!(has_memory_tool);
}

Testing Best Practices

Prefer Integration Tests

Per project guidelines, prefer tests in tests/ directories:
crates/goose/tests/
  ├── agent.rs
  ├── providers.rs
  └── mcp_integration_test.rs

Update Self-Test Recipe

When adding features, update goose-self-test.yaml:
# After updating the recipe
cargo build
goose run --recipe goose-self-test.yaml

Use Anyhow for Tests

Test functions can return Result<()>:
use anyhow::Result;

#[test]
fn test_with_result() -> Result<()> {
    let value = risky_operation()?;
    assert_eq!(value, 42);
    Ok(())
}

Avoid Flaky Tests

  • Don’t rely on timing/sleep
  • Use deterministic test data
  • Mock external dependencies
  • Clean up resources in tests

Test Coverage

Aim to test:
  • Happy path functionality
  • Error cases
  • Edge cases (empty input, large input, etc.)
  • Async behavior and cancellation
  • Resource cleanup

Continuous Integration

Tests run on every PR via GitHub Actions (.github/workflows/ci.yml). Locally, run the same checks:
just check-everything
This runs:
  • cargo fmt - Code formatting
  • cargo clippy - Linting
  • cargo test - All tests
  • UI linting
  • OpenAPI schema validation

Debugging Tests

Show Test Output

cargo test -- --nocapture

Run Single Test

cargo test test_name -- --exact

Enable Logging

RUST_LOG=debug cargo test

Filter Tests

# Run tests matching pattern
cargo test provider

# Run tests in specific module
cargo test providers::tests

Performance Testing

Local inference performance tests:
cargo test --package goose --test local_inference_perf --release

Next Steps

Build docs developers (and LLMs) love