| name | clickup-local-dev-loop |
| description | Set up local development for ClickUp API integrations with testing,
mocking, and hot reload.
Trigger: "clickup dev setup", "clickup local development", "clickup dev environment",
"develop with clickup", "clickup testing setup", "mock clickup API".
|
| allowed-tools | Read, Write, Edit, Bash(npm:*), Bash(pnpm:*), Grep |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
| tags | ["saas","productivity","clickup"] |
| compatible-with | claude-code |
ClickUp Local Dev Loop
Overview
Set up a fast local development workflow for ClickUp API v2 integrations with hot reload, mocking, and integration testing.
Project Setup
mkdir my-clickup-integration && cd $_
npm init -y
npm install -D typescript tsx vitest @types/node dotenv
npx tsc --init --target ES2022 --module nodenext --outDir dist
my-clickup-integration/
├── src/
│ ├── clickup/
│ │ ├── client.ts # ClickUp API client (see clickup-sdk-patterns)
│ │ ├── types.ts # TypeScript interfaces
│ │ └── tasks.ts # Task operations
│ └── index.ts
├── tests/
│ ├── mocks/
│ │ └── clickup.ts # Mock ClickUp API responses
│ ├── unit/
│ │ └── tasks.test.ts
│ └── integration/
│ └── clickup.test.ts
├── .env.local # Local secrets (git-ignored)
├── .env.example # Template for team
└── package.json
Environment Configuration
CLICKUP_API_TOKEN=pk_your_token_here
CLICKUP_TEAM_ID=your_team_id
CLICKUP_TEST_LIST_ID=your_test_list_id
cp .env.example .env.local
{
"scripts": {
"dev": "tsx watch src/index.ts",
"test": "vitest",
"test:watch": "vitest --watch",
"test:integration": "CLICKUP_LIVE=1 vitest --run tests/integration/",
"build": "tsc",
"typecheck": "tsc --noEmit"
}
}
Mock ClickUp API for Unit Tests
export const mockTask = {
id: 'test_task_001',
name: 'Test Task',
status: { status: 'to do', color: '#d3d3d3', type: 'open' },
priority: { id: '3', priority: 'normal', color: '#6fddff' },
date_created: '1695000000000',
date_updated: '1695000000000',
due_date: null,
assignees: [],
tags: [],
url: 'https://app.clickup.com/t/test_task_001',
list: { id: '900100200300', name: 'Test List' },
folder: { id: '456', name: 'Test Folder' },
space: { id: '789' },
custom_fields: [],
};
export const mockTeam = {
teams: [{
id: '1234567',
name: 'Test Workspace',
members: [{ user: { id: , : , : } }],
}],
};
() {
vi.( (: , ?: ) => {
path = (url)..(, );
: <, > = {
: mockTeam,
: { : { : , : } },
};
(path.()) (mockTask);
(path.() && options?. === ) {
({ ...mockTask, ....(options. ) });
}
(routes[path] ?? {}, routes[path] ? : );
});
}
() {
(.(data), {
status,
: {
: ,
: ,
: ,
: (.(.() / ) + ),
},
});
}
Unit Test Example
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { mockClickUpFetch, mockTask } from '../mocks/clickup';
describe('ClickUp Task Operations', () => {
beforeEach(() => {
vi.stubGlobal('fetch', mockClickUpFetch());
vi.stubEnv('CLICKUP_API_TOKEN', 'pk_test_token');
});
it('creates a task with required fields', async () => {
const { createTask } = await import('../../src/clickup/tasks');
const task = await createTask('list123', { name: 'New Task' });
expect(task.name).toBe('New Task');
expect(fetch).toHaveBeenCalledWith(
expect.stringContaining('/list/list123/task'),
expect.objectContaining({ method: 'POST' }),
);
});
it('gets a task by ID', async () => {
const { getTask } = await ();
task = ();
(task.).(mockTask.);
});
});
Integration Test (Live API)
import { describe, it, expect } from 'vitest';
import 'dotenv/config';
const LIVE = process.env.CLICKUP_LIVE === '1';
describe.skipIf(!LIVE)('ClickUp Live API', () => {
it('authenticates and lists workspaces', async () => {
const response = await fetch('https://api.clickup.com/api/v2/team', {
headers: { 'Authorization': process.env.CLICKUP_API_TOKEN! },
});
expect(response.ok).toBe(true);
const data = await response.json();
expect(data.teams.length).toBeGreaterThan(0);
});
});
Error Handling
| Error | Cause | Solution |
|---|
CLICKUP_API_TOKEN undefined | Missing .env.local | Copy from .env.example |
| Integration tests fail | No live token | Set CLICKUP_LIVE=1 and valid token |
| Mock not matching | Route pattern wrong | Check URL path in mock router |
Resources
Next Steps
See clickup-sdk-patterns for production-ready client patterns.