Testing AI Agents in TypeScript: Unit Tests, Mocks & Integration Testing
Testing AI Agents in TypeScript: Unit Tests, Mocks & Integration Testing: Designed as a zero-dependency, open-source TypeScript architecture under the MIT License with native Model Context Protocol (MCP) support and deterministic phase state machines.
- Mock LLM providers for deterministic, offline agent tests
- Test tool execution order and input/output contracts
- Validate permission gate behaviour without human interaction
- Snapshot testing for agent-generated file content
Mocking the LLM Provider
The key to fast, deterministic agent tests is replacing the live LLM with a scripted mock that returns predefined tool calls and responses:
import { describe, it, expect, vi } from 'vitest';import { createAgent } from 'smoke-monkey-harness';// Mock the LLM provider to return a scripted tool call sequencevi.mock('smoke-monkey-harness/providers', () => ({createProvider: () => ({complete: vi.fn()// First call: agent decides to read a file.mockResolvedValueOnce({toolCalls: [{ name: 'read_file', args: { path: 'src/utils.ts' } }],})// Second call: agent writes the fix.mockResolvedValueOnce({toolCalls: [{ name: 'replace_file_content', args: {path: 'src/utils.ts',oldText: 'var x = 1',newText: 'const x = 1',}}],})// Third call: agent completes.mockResolvedValueOnce({ content: 'Done. Replaced var with const.' }),}),}));describe('refactoring agent', () => {it('replaces var with const in target file', async () => {const agent = createAgent({ provider: 'anthropic', model: 'claude-3-7-sonnet', workspacePath: '/tmp/test' });const result = await agent.run('Replace all var declarations with const');expect(result.toolCallCount).toBe(2);expect(result.summary).toContain('const');});});
Integration Testing with Real Models
For integration tests that validate end-to-end agent behaviour against real LLMs:
- Use a cheap, fast model like
claude-3-haikuorgpt-4o-minito keep costs low. - Scope tasks tightly — give the agent a small, well-defined task with a verifiable outcome.
- Assert on outcomes, not on the exact sequence of tool calls (which may vary between runs).
- Set maxIterations low (e.g., 5) to prevent runaway integration tests.
Run integration tests in CI only on PRs to main, not on every feature branch commit.
Never use production API keys in CI integration tests
Use test-specific API keys with low rate limits and spending caps. Tag all integration test requests with a custom header for cost attribution.
Frequently Asked Questions
Q:How do I test that a permission gate correctly blocks a dangerous tool call?
Set the tool permission to "deny" and assert that the agent emits a permission.denied event for that tool call, and that the tool was never executed (verify via the mock).
Q:Can I use Jest instead of Vitest?
Yes. Smoke Monkey Harness is test-framework agnostic. Replace vi.mock with jest.mock and vi.fn with jest.fn — the mocking pattern is identical.
Related Alternatives & Comparisons
Build with Smoke Monkey Harness
Zero dependencies. 24 built-in tools. Human-in-the-loop safety. 100% open source under the MIT License.