Skip to main content

Test Layers

The MCP server has three test layers, each exercising a different boundary:
LayerWhat it testsTransportGiza SDK
UnitIndividual tool handlers, formatters, config resolutionNone (direct function calls)Mocked
IntegrationMCP protocol: tool registration, Zod schema wiring, session state, error propagationInMemoryTransport (in-process)Mocked
E2EFull process: CLI startup, stdio/HTTP transport, real MCP handshakeStdioClientTransport / StreamableHTTPClientTransport (out-of-process)Real SDK (env-configured)

Running Tests

From the packages/mcp-server/ directory:
# Unit tests
bun run test

# Integration tests
bun run test:integration

# E2E tests (builds first, then runs)
bun run test:e2e

# Unit tests with coverage
bun run test:cov
All three layers also run under bun test from the package directory. E2E tests automatically skip when dist/cli.js is missing (no build) or when running from a different working directory.

Integration Tests

Integration tests wire an MCP Client to a real GizaServer via InMemoryTransport, with the Giza SDK mocked. This exercises the full MCP protocol layer without spawning processes.

Structure

src/__integration__/
  helpers/
    mock-giza.ts              # Giza SDK mock factory
    mcp-client.ts             # InMemoryTransport test harness
  server-lifecycle.test.ts    # Construction, McpServer instance
  tool-discovery.test.ts      # Tool listing, schema validation
  tool-execution.test.ts      # Tool calls through the protocol
  wallet-session.test.ts      # Cross-tool session state
  error-propagation.test.ts   # Error type mapping through MCP
  prompt-registration.test.ts # System prompt via MCP prompts API
  cherry-picked-tools.test.ts # Subset tool registration

Test Harness

Use createTestHarness() to get a connected client with mocked SDK:
import { createTestHarness } from './helpers/mcp-client.js';

const { client, mockGiza, cleanup } = await createTestHarness();

// Call tools through the MCP protocol
const result = await client.callTool({
  name: 'connect_wallet',
  arguments: { wallet: '0x1234567890abcdef1234567890abcdef12345678' },
});

// Assert on the result
expect(result.isError).toBeFalsy();

// Access the mock to verify SDK calls
expect(mockGiza.agent).toHaveBeenCalledWith('0x...');

// Always clean up
await cleanup();
Pass overrides to test with a subset of tools or a custom system prompt:
import { walletTools, protocolTools } from '../tools/index.js';

const { client, cleanup } = await createTestHarness({
  tools: [...walletTools, ...protocolTools],
  systemPrompt: 'Custom prompt for testing',
});

What Integration Tests Cover

  • Server lifecycle: Construction, McpServer instance type, tool accessibility after connection
  • Tool discovery: Correct tool count, schema structure, name uniqueness, exact tool name set
  • Tool execution: connect_wallet success, get_tokens without wallet, get_portfolio with/without wallet, optimize parameter forwarding
  • Wallet session: Connect-then-operate flow, disconnect clears state, second connect overwrites, state persists across calls
  • Error propagation: All error types (WalletNotConnectedError, GizaAPIError, TimeoutError, NetworkError, GizaError, generic Error, non-Error throws) map to isError: true with correct messages
  • Prompt registration: listPrompts returns system prompt, getPrompt returns default text, custom prompt override works
  • Cherry-picked tools: Subset registration (2 tools, 6 tools), calling unregistered tool returns error

E2E Tests

E2E tests spawn the built CLI as a child process and connect to it using real MCP client transports.

Structure

src/__e2e__/
  helpers/
    stdio-client.ts           # Spawns CLI via StdioClientTransport
    http-client.ts            # Spawns HTTP server, connects StreamableHTTPClientTransport
  stdio-transport.test.ts     # Stdio transport through real process
  http-transport.test.ts      # HTTP transport with /health and /mcp endpoints
  wallet-errors.test.ts       # Wallet errors through real MCP protocol
  validation.test.ts          # Zod schema validation through MCP protocol

Prerequisites

E2E tests require a build:
bun run build
The test:e2e script handles this automatically. When running via bun test, E2E suites skip if dist/cli.js doesn’t exist.

Environment

E2E tests inject test environment variables into the spawned process:
VariableTest Value
GIZA_API_KEYtest-key
GIZA_PARTNER_NAMEtest
GIZA_API_URLhttps://api.test.giza.tech
CHAIN_ID8453

What E2E Tests Cover

  • Stdio transport: Client connection, listTools returns all tools, listPrompts, wallet connect with valid/invalid addresses
  • HTTP transport: /health returns { status: "ok" }, unknown routes return 404, MCP client connects and lists tools, connect_wallet through HTTP
  • Wallet errors: get_portfolio and activate_agent without wallet return errors (not crashes), disconnect_wallet without prior connect returns “No wallet” message
  • Validation: Empty string and non-hex wallet addresses rejected by Zod schemas through the MCP protocol

Adding New Tests

Adding an integration test

  1. Create a new .test.ts file in src/__integration__/
  2. Import createTestHarness from the helpers
  3. Use client.callTool(), client.listTools(), or client.listPrompts() to exercise the MCP protocol
  4. If you need to configure mock behavior, access mockGiza.__mockAgent for agent methods or mockGiza directly for top-level SDK methods

Adding an E2E test

  1. Create a new .test.ts file in src/__e2e__/
  2. Import helpers and isE2EAvailable from ./helpers/stdio-client.js
  3. Use conditional describe: const run = isE2EAvailable() ? describe : describe.skip;
  4. Use createStdioClient() or createHttpClient(port) in beforeAll
  5. Guard afterAll with if (cleanup) await cleanup()