Agent Skills: HubSpot Local Dev Loop

|

UncategorizedID: jeremylongshore/claude-code-plugins-plus-skills/hubspot-local-dev-loop

Install this agent skill to your local

pnpm dlx add-skill https://github.com/jeremylongshore/claude-code-plugins-plus-skills/tree/HEAD/plugins/saas-packs/hubspot-pack/skills/hubspot-local-dev-loop

Skill Files

Browse the full folder contents for hubspot-local-dev-loop.

Download Skill

Loading file tree…

plugins/saas-packs/hubspot-pack/skills/hubspot-local-dev-loop/SKILL.md

Skill Metadata

Name
hubspot-local-dev-loop
Description
|

HubSpot Local Dev Loop

Overview

Set up a fast local development workflow for HubSpot integrations with sandbox accounts, mocking, and test utilities.

Prerequisites

  • Completed hubspot-install-auth setup
  • Node.js 18+ with npm/pnpm
  • HubSpot developer test account (free at developers.hubspot.com)

Instructions

Step 1: Create Project Structure

my-hubspot-project/
├── src/
│   ├── hubspot/
│   │   ├── client.ts       # Singleton @hubspot/api-client wrapper
│   │   ├── contacts.ts     # Contact operations
│   │   ├── deals.ts        # Deal operations
│   │   └── types.ts        # HubSpot type definitions
│   └── index.ts
├── tests/
│   ├── mocks/
│   │   └── hubspot.ts      # Shared mock factory
│   ├── contacts.test.ts
│   └── deals.test.ts
├── .env.local               # Local secrets (git-ignored)
├── .env.example             # Template for team
├── tsconfig.json
└── package.json

Step 2: Create Client Singleton

// src/hubspot/client.ts
import * as hubspot from '@hubspot/api-client';

let instance: hubspot.Client | null = null;

export function getHubSpotClient(): hubspot.Client {
  if (!instance) {
    if (!process.env.HUBSPOT_ACCESS_TOKEN) {
      throw new Error('HUBSPOT_ACCESS_TOKEN environment variable is required');
    }
    instance = new hubspot.Client({
      accessToken: process.env.HUBSPOT_ACCESS_TOKEN,
      numberOfApiCallRetries: 3,
    });
  }
  return instance;
}

// Reset client (useful for tests)
export function resetHubSpotClient(): void {
  instance = null;
}

Step 3: Configure Testing with Vitest

{
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "test": "vitest",
    "test:watch": "vitest --watch",
    "test:integration": "HUBSPOT_TEST=true vitest --config vitest.integration.config.ts"
  },
  "devDependencies": {
    "@hubspot/api-client": "^13.0.0",
    "vitest": "^2.0.0",
    "tsx": "^4.0.0"
  }
}

Step 4: Mock HubSpot API for Unit Tests

// tests/mocks/hubspot.ts
import { vi } from 'vitest';

export function createMockHubSpotClient() {
  return {
    crm: {
      contacts: {
        basicApi: {
          create: vi.fn().mockResolvedValue({
            id: '101',
            properties: { firstname: 'Jane', lastname: 'Doe', email: 'jane@test.com' },
            createdAt: new Date().toISOString(),
            updatedAt: new Date().toISOString(),
            archived: false,
          }),
          getById: vi.fn().mockResolvedValue({
            id: '101',
            properties: { firstname: 'Jane', lastname: 'Doe', email: 'jane@test.com' },
          }),
          getPage: vi.fn().mockResolvedValue({
            results: [],
            paging: undefined,
          }),
          update: vi.fn().mockResolvedValue({ id: '101', properties: {} }),
          archive: vi.fn().mockResolvedValue(undefined),
        },
        searchApi: {
          doSearch: vi.fn().mockResolvedValue({ total: 0, results: [] }),
        },
        batchApi: {
          create: vi.fn().mockResolvedValue({ status: 'COMPLETE', results: [] }),
          read: vi.fn().mockResolvedValue({ status: 'COMPLETE', results: [] }),
          update: vi.fn().mockResolvedValue({ status: 'COMPLETE', results: [] }),
        },
      },
      deals: {
        basicApi: {
          create: vi.fn().mockResolvedValue({
            id: '201',
            properties: { dealname: 'Test Deal', amount: '1000' },
          }),
          getById: vi.fn(),
          update: vi.fn(),
        },
      },
      companies: {
        basicApi: {
          create: vi.fn().mockResolvedValue({
            id: '301',
            properties: { name: 'Test Co', domain: 'test.com' },
          }),
        },
      },
    },
  };
}

// tests/contacts.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { createMockHubSpotClient } from './mocks/hubspot';

vi.mock('../src/hubspot/client', () => ({
  getHubSpotClient: vi.fn(),
}));

describe('Contact operations', () => {
  const mockClient = createMockHubSpotClient();

  beforeEach(() => {
    vi.mocked(getHubSpotClient).mockReturnValue(mockClient as any);
  });

  it('should create a contact with required properties', async () => {
    const result = await mockClient.crm.contacts.basicApi.create({
      properties: { firstname: 'Jane', lastname: 'Doe', email: 'jane@test.com' },
      associations: [],
    });
    expect(result.id).toBe('101');
    expect(result.properties.email).toBe('jane@test.com');
  });
});

Step 5: Developer Test Account

# Create a free developer test account at:
# https://developers.hubspot.com/get-started

# Use test account token for integration tests
# .env.local
HUBSPOT_ACCESS_TOKEN=pat-na1-test-xxxx  # test account token
HUBSPOT_PORTAL_ID=12345678              # test portal ID

Output

  • Client singleton with reset capability for tests
  • Mock factory covering contacts, deals, companies
  • Unit tests running without API calls
  • Integration tests using developer test account
  • Hot-reload dev loop with tsx watch

Error Handling

| Error | Cause | Solution | |-------|-------|----------| | HUBSPOT_ACCESS_TOKEN is required | Missing env var | Copy .env.example to .env.local | | Mock type mismatch | SDK version change | Update mock factory to match SDK types | | Test timeout | Real API call leaked | Verify mocks are wired correctly | | 429 in integration tests | Rate limited on test account | Add delay between test runs |

Examples

Integration Test with Real API

// tests/integration/contacts.integration.test.ts
import { describe, it, expect } from 'vitest';
import * as hubspot from '@hubspot/api-client';

const shouldRun = process.env.HUBSPOT_TEST === 'true';

describe.skipIf(!shouldRun)('HubSpot Integration', () => {
  const client = new hubspot.Client({
    accessToken: process.env.HUBSPOT_ACCESS_TOKEN!,
  });

  it('should create and archive a test contact', async () => {
    const contact = await client.crm.contacts.basicApi.create({
      properties: {
        firstname: 'Integration',
        lastname: `Test-${Date.now()}`,
        email: `test-${Date.now()}@example.com`,
      },
      associations: [],
    });
    expect(contact.id).toBeDefined();

    // Clean up
    await client.crm.contacts.basicApi.archive(contact.id);
  });
});

Resources

Next Steps

See hubspot-sdk-patterns for production-ready code patterns.