CrewAI Tutorial (TypeScript): testing agents locally for intermediate developers

By Cyprian AaronsUpdated 2026-04-21
crewaitesting-agents-locally-for-intermediate-developerstypescript

This tutorial shows you how to run CrewAI agents locally in TypeScript, wire them to a test-friendly mock model, and verify agent behavior without burning API calls. You need this when you want fast feedback on prompts, tools, and task wiring before you point the same code at a real LLM.

What You'll Need

  • Node.js 18+ installed
  • A TypeScript project initialized with npm or pnpm
  • CrewAI packages for TypeScript:
    • crewai
    • @langchain/openai
    • zod
  • An OpenAI API key only if you want to swap from local testing to a real model later
  • A terminal and a code editor
  • Basic familiarity with agents, tasks, and tools in CrewAI

Step-by-Step

  1. Install the dependencies and set up a minimal TypeScript project.
    Keep this local test harness small so you can iterate on agent behavior without extra framework noise.
mkdir crewai-local-test
cd crewai-local-test
npm init -y
npm install crewai @langchain/openai zod
npm install -D typescript tsx @types/node
npx tsc --init
  1. Create a local mock model that behaves like an LLM but returns deterministic output.
    This is the key to testing agents locally: your tests should not depend on network calls or changing model output.
// src/mock-chat-model.ts
import { BaseChatModel } from "@langchain/core/language_models/chat_models";
import { AIMessage, BaseMessage } from "@langchain/core/messages";
import { ChatResult } from "@langchain/core/outputs";

export class MockChatModel extends BaseChatModel {
  _llmType() {
    return "mock-chat-model";
  }

  async _generate(messages: BaseMessage[]): Promise<ChatResult> {
    const last = messages[messages.length - 1]?.content?.toString() ?? "";
    const content = last.toLowerCase().includes("risk")
      ? "Risk reviewed: medium. Escalate if exposure exceeds threshold."
      : "Approved. No issues found.";
    return {
      generations: [{ message: new AIMessage(content), text: content }],
      llmOutput: {},
    };
  }
}
  1. Define a simple tool and an agent that uses it.
    In local testing, keep the tool deterministic too, so you can assert on exact outputs.
// src/tools.ts
import { tool } from "@langchain/core/tools";
import { z } from "zod";

export const policyLookupTool = tool(
  async ({ policyId }: { policyId: string }) => {
    return JSON.stringify({
      policyId,
      status: "active",
      limit: 50000,
      currency: "USD",
    });
  },
  {
    name: "policy_lookup",
    description: "Fetch policy details by policy ID.",
    schema: z.object({
      policyId: z.string().describe("The policy identifier"),
    }),
  }
);
  1. Wire the agent, task, and crew together in one executable file.
    This example keeps the structure close to production CrewAI usage while swapping in the mock model for local runs.
// src/index.ts
import { Agent, Crew, Task } from "crewai";
import { MockChatModel } from "./mock-chat-model";
import { policyLookupTool } from "./tools";

async function main() {
  const model = new MockChatModel();

  const agent = new Agent({
    name: "Underwriter",
    role: "Insurance underwriter",
    goal: "Review policy requests and flag risk",
    backstory: "You review policies for basic eligibility checks.",
    llm: model,
    tools: [policyLookupTool],
  });

  const task = new Task({
    description:
      "Review policy P-1001 using the lookup tool and decide whether it should be approved.",
    expectedOutput: "A short underwriting decision with risk assessment.",
    agent,
  });

  const crew = new Crew({
    agents: [agent],
    tasks: [task],
  });

  const result = await crew.kickoff();
  console.log(String(result));
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});
  1. Add a small test script so you can run the same flow repeatedly during development.
    This gives you a stable local check before you connect real credentials or deploy the agent into a larger workflow.
{
  "name": "crewai-local-test",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "start": "tsx src/index.ts"
  }
}
  1. Run it locally and inspect the result.
    If everything is wired correctly, you should get deterministic underwriting output every time.
npm run start

Testing It

Run the script several times and confirm the output does not change between executions. That tells you your agent logic is isolated from external LLM variability.

Next, change the task description to include words like “risk”, then run again and verify the mock model switches to the risk branch. If that works, your local harness is correctly exercising prompt-dependent behavior.

If you want stronger validation, wrap crew.kickoff() in a Node test runner like Vitest or Jest and assert on the returned string. The important part is that your tests validate orchestration and output shape before you introduce network calls.

Next Steps

  • Replace MockChatModel with ChatOpenAI when you want to test against a real model.
  • Add more tools with strict Zod schemas so invalid inputs fail fast.
  • Move this setup into Vitest tests and snapshot the agent outputs for regression coverage

Keep learning

By Cyprian Aarons, AI Consultant at Topiax.

Want the complete 8-step roadmap?

Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.

Get the Starter Kit

Related Guides