Skills Development HubSpot Local Dev Loop Setup Guide

HubSpot Local Dev Loop Setup Guide

v20260423
hubspot-local-dev-loop
A comprehensive guide to setting up a robust and fast local development workflow for HubSpot integrations. This setup includes creating a client singleton, configuring unit and integration tests, and implementing API mocking utilities (using Vitest) to ensure development can proceed efficiently without relying on live HubSpot API calls or requiring excessive sandbox usage. Perfect for building reliable, scalable CRM connectors.
Get Skill
387 downloads
Overview

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.

Info
Category Development
Name hubspot-local-dev-loop
Version v20260423
Size 7.27KB
Updated At 2026-04-28
Language