Documentation Index
Fetch the complete documentation index at: https://mintlify.com/block/goose/llms.txt
Use this file to discover all available pages before exploring further.
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:
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:
This runs tests and records MCP interactions to crates/goose/tests/mcp_replays/.
UI Tests
Test the desktop UI:
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:
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
Local inference performance tests:
cargo test --package goose --test local_inference_perf --release
Next Steps