> ## Documentation Index
> Fetch the complete documentation index at: https://developers.hubspot.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

---
id: 0eba3f30-1f84-4e85-83ec-41106ed67d2e
---

# Mocking UI extensions

> Mock the behavior of your UI extensions with utilities provided by the UI extensions SDK.

The UI extensions SDK provides comprehensive mocking support for testing extensions without making real API calls or requiring access to actual HubSpot data. You can mock extension context data, React hooks, extension actions, and serverless function calls.

The mocking functionality is built on [tinyspy](https://github.com/tinylibs/tinyspy), a minimal spy library. Each mock function is created using tinyspy's `spyOn` function, which means all mock functions expose the standard tinyspy spy properties and methods for advanced testing scenarios.

## Quick reference

**Mock Types Available:**

* [`mocks.context`](#mocking-context): extension point-specific context data
* [`mocks.actions`](#mocking-actions): extension point-specific actions (spies)
* [`mocks.runServerlessFunction()`](#mocking-runserverlessfunction): serverless function calls (spy)
* [`mocks.useCrmProperties()`](#mocking-usecrmproperties): CRM properties hook (spy)
* [`mocks.useAssociations()`](#mocking-useassociations): CRM associations hook (spy)

**Spy Methods:**

* [`nextResult(value)`](#mocking-the-next-result): set the return value for the next single call only
* [`willCall(fn)`](#custom-mock-functions): provide a custom implementation that persists across all calls
* [`reset()`](#resetting-mocks): clear call history while keeping the mock implementation
* [`restore()`](#restoring-original-implementation): restore to the default mock behavior

**Spy Properties:**

* [`called`](#spy-properties): boolean indicating if called
* [`callCount`](#spy-properties): number of times called
* [`calls`](#spy-properties): array of argument arrays
* [`returns`](#spy-properties): array of return values

## Automatic mocks based on extension point location

When you create a renderer with [`createRenderer(extensionPointLocation)`](/apps/developer-platform/add-features/ui-extensions/tools/testing/reference#createrenderer), the testing SDK automatically creates appropriate mocks for the Extension Point API based on the location you specify. This includes:

* `context`: Extension point-specific context data (user info, portal info, CRM data, variables, etc.)
* `actions`: Extension point-specific actions (addAlert, reloadPage, fetchCrmObjectProperties, etc.)
* `runServerlessFunction()`: A mock implementation for calling serverless functions

<Tip>
  A renderer created with `createRenderer('crm.record.tab')` will provide different context and actions than one created with `createRenderer('settings')`. Always use the extension point location that matches where your component will be used.
</Tip>

## Mocking React hooks

### Default mock implementations

The testing SDK automatically provides default mock implementations for supported hooks. These defaults allow your components to render without additional configuration:

* **`useCrmProperties()`**: Returns fake property values based on property names (e.g., `firstname` → `fake_firstname`)
* **`useAssociations()`**: Returns a single fake association result with fake property values

```tsx theme={null}
import { createRenderer } from '@hubspot/ui-extensions/testing';
import { useCrmProperties } from '@hubspot/ui-extensions/crm';
import { Text } from '@hubspot/ui-extensions';

function MyComponent() {
  const { properties, isLoading, error } = useCrmProperties([
    'firstname',
    'lastname',
  ]);
  if (isLoading) {
    return <Text>Loading...</Text>;
  }
  if (error) {
    return <Text>Something went wrong!</Text>;
  }
  return (
    <>
      <Text>First name: {properties.firstname}</Text>
      <Text>Last name: {properties.lastname}</Text>
    </>
  );
}

const { render, findAll } = createRenderer('crm.record.tab');
render(<MyComponent />);

const textNodes = findAll(Text);
expect(textNodes[0].text).toEqual('First name: fake_firstname');
expect(textNodes[1].text).toEqual('Last name: fake_lastname');
```

### Mocking the next result

Use the `nextResult()` method to mock the return value for the next single invocation of a hook. This is useful for testing specific states like loading or error conditions. The mock applies only to the next call and then reverts to the default behavior:

```tsx theme={null}
import { createRenderer } from '@hubspot/ui-extensions/testing';
import { useCrmProperties } from '@hubspot/ui-extensions/crm';
import { Text } from '@hubspot/ui-extensions';

function MyComponent() {
  const { properties, isLoading, error } = useCrmProperties([
    'firstname',
    'lastname',
  ]);
  if (isLoading) {
    return <Text>Loading...</Text>;
  }
  if (error) {
    return <Text>Something went wrong!</Text>;
  }
  return (
    <>
      <Text>First name: {properties.firstname}</Text>
      <Text>Last name: {properties.lastname}</Text>
    </>
  );
}

const {find, render, mocks } = createRenderer('crm.record.tab');

// Mock an error state
mocks.useCrmProperties.nextResult({
  properties: {},
  error: new Error('Something went wrong!'),
  isLoading: false,
});

render(<MyComponent />);
expect(find(Text).text).toEqual('Something went wrong!');
```

### Custom mock functions

Use the `willCall()` method to provide a custom implementation for a hook that persists across all calls. This gives you full control over the hook's behavior based on the input arguments. Unlike `nextResult()`, which applies to only the next call, `willCall()` replaces the mock implementation for all subsequent calls:

```tsx theme={null}
import { createRenderer } from '@hubspot/ui-extensions/testing';
import { useCrmProperties } from '@hubspot/ui-extensions/crm';
import { Text } from '@hubspot/ui-extensions';

function MyComponent() {
  const { properties, isLoading, error } = useCrmProperties([
    'firstname',
    'lastname',
  ]);
  if (isLoading) {
    return <Text>Loading...</Text>;
  }
  if (error) {
    return <Text>Something went wrong!</Text>;
  }
  return (
    <>
      <Text>First name: {properties.firstname}</Text>
      <Text>Last name: {properties.lastname}</Text>
    </>
  );
}

const { render, mocks, findAll } = createRenderer('crm.record.tab');

// Provide a custom mock implementation
mocks.useCrmProperties.willCall((propertyNames) => {
  const properties = propertyNames.reduce(
    (acc, propertyName) => {
      acc[propertyName] = propertyName.toUpperCase();
      return acc;
    },
    {} as Record<string, string>
  );

  return {
    properties,
    error: null,
    isLoading: false,
  };
});

render(<MyComponent />);

const textNodes = findAll(Text);
expect(textNodes[0].text).toEqual('First name: FIRSTNAME');
expect(textNodes[1].text).toEqual('Last name: LASTNAME');
```

### Mocking useAssociations

The `useAssociations()` hook supports the same mocking methods as `useCrmProperties()`:

```tsx theme={null}
import { createRenderer } from '@hubspot/ui-extensions/testing';
import { useAssociations } from '@hubspot/ui-extensions/crm';
import { Text } from '@hubspot/ui-extensions';

function MyComponent() {
  const { results, isLoading, error } = useAssociations({
    toObjectType: '0-1',
    properties: ['firstname', 'lastname'],
    pageLength: 10,
  });
  if (isLoading) {
    return <Text>Loading...</Text>;
  }
  if (error) {
    return <Text>Something went wrong!</Text>;
  }
  return (
    <>
      {results.map((result) => (
        <Text key={result.toObjectId}>
          {result.properties.firstname} {result.properties.lastname}
        </Text>
      ))}
    </>
  );
}

const { find, render, mocks } = createRenderer('crm.record.tab');

// Mock an error state for the next call only
mocks.useAssociations.nextResult({
  results: [],
  error: new Error('Something went wrong!'),
  isLoading: false,
});

render(<MyComponent />);
expect(find(Text).text).toEqual('Something went wrong!');
```

## Mocking Extension Point API

The testing SDK automatically creates mocks for the Extension Point API based on the extension point location you provide to `createRenderer()`. These mocks allow you to test components that use `context`, `actions`, or `runServerlessFunction()` from the Extension Point API.

### Mocking context

The `context` object is automatically populated with fake data appropriate for the extension point location. You can access and customize it via `mocks.context`:

```tsx theme={null}
import { createRenderer } from '@hubspot/ui-extensions/testing';
import { Text } from '@hubspot/ui-extensions';

function MyComponent({ context }) {
  return (
    <>
      <Text>User: {context.user.firstName} {context.user.lastName}</Text>
      <Text>Email: {context.user.email}</Text>
      <Text>Portal ID: {context.portal.id}</Text>
      {context.crm && <Text>Object ID: {context.crm.objectId}</Text>}
    </>
  );
}

const { render, mocks, findAll } = createRenderer('crm.record.tab');

// Access and verify the mock context values
expect(mocks.context.user.email).toEqual('fake_email@example.com');
expect(mocks.context.user.firstName).toEqual('fake_firstName');
expect(mocks.context.portal.id).toEqual(123);
expect(mocks.context.crm.objectId).toEqual(123);

render(<MyComponent context={mocks.context} />);

const textNodes = findAll(Text);
expect(textNodes[0].text).toEqual('User: fake_firstName fake_lastName');
expect(textNodes[1].text).toEqual('Email: fake_email@example.com');
```

You can customize the context before rendering:

```tsx theme={null}
const { findAll, render, mocks } = createRenderer('crm.record.tab');

// Customize context values for your test
mocks.context.user.firstName = 'Alice';
mocks.context.user.lastName = 'Johnson';
mocks.context.portal.id = 456;

render(<MyComponent context={mocks.context} />);
const textNodes = findAll(Text);
expect(textNodes[0].text).toEqual('User: Alice Johnson');
```

The shape of the `context` object depends on the extension point location:

* **CRM locations** (`'crm.record.tab'`, `'crm.record.sidebar'`, `'crm.preview'`, `'helpdesk.sidebar'`): Include `user`, `portal`, `crm` (with `objectId` and `objectTypeId`), and `variables`
* **Settings location** (`'settings'`): Include `user` and `portal`
* **Home location** (`'home'`): Include `user` and `portal`

### Mocking actions

The `actions` object contains extension point-specific functions that are automatically mocked as spies. You can use these spies to verify that your component calls the correct actions:

```tsx theme={null}
import { createRenderer } from '@hubspot/ui-extensions/testing';
import { Button } from '@hubspot/ui-extensions';

function MyComponent({ actions }) {
  return (
    <Button
      onClick={() => {
        actions.addAlert({
          type: 'success',
          message: 'Action completed!',
        });
      }}
    >
      Trigger Alert
    </Button>
  );
}

const { render, mocks, find } = createRenderer('crm.record.tab');

render(<MyComponent actions={mocks.actions} />);

// Trigger the button click
find(Button).trigger('onClick');

// Verify the action was called
expect(mocks.actions.addAlert.called).toBe(true);
expect(mocks.actions.addAlert.callCount).toBe(1);
expect(mocks.actions.addAlert.calls[0]).toEqual([
  {
    type: 'success',
    message: 'Action completed!',
  },
]);
```

Available actions vary by extension point location:

* **CRM locations**: `addAlert()`, `reloadPage()`, `fetchCrmObjectProperties()`, `openIframeModal()`, `refreshObjectProperties()`, `onCrmPropertiesUpdate()`, `copyTextToClipboard()`, `closeOverlay()`
* **Settings location**: `addAlert()`, `copyTextToClipboard()`, `closeOverlay()`, `reloadPage()`, `openIframeModal()`
* **Home location**: `addAlert()`, `copyTextToClipboard()`, `closeOverlay()`, `reloadPage()`, `openIframeModal()`

### Mocking serverless functions

The `runServerlessFunction()` mock allows you to test components that call serverless functions:

```tsx theme={null}
import { useState } from 'react';
import { createRenderer } from '@hubspot/ui-extensions/testing';
import { Button, Text } from '@hubspot/ui-extensions';

function MyComponent({ runServerlessFunction }) {
  const [data, setData] = useState(null);

  const handleClick = async () => {
    const result = await runServerlessFunction({
      name: 'myFunction',
      parameters: { param1: 'value1' },
    });
    if (result.status === 'SUCCESS') {
      setData(result.response);
    }
  };

  return (
    <>
      <Button onClick={handleClick}>Fetch Data</Button>
      {data && <Text>{data.message}</Text>}
    </>
  );
}

const { render, mocks, find, waitFor } = createRenderer('crm.record.tab');

// Mock the serverless function response
mocks.runServerlessFunction.nextResult(
  Promise.resolve({
    status: 'SUCCESS',
    response: { message: 'Hello from serverless!' },
  })
);

render(<MyComponent runServerlessFunction={mocks.runServerlessFunction} />);

// Trigger the function call
find(Button).trigger('onClick');

// Wait for the async update
await waitFor(() => {
  expect(find(Text).text).toEqual('Hello from serverless!');
});

// Verify the function was called correctly
expect(mocks.runServerlessFunction.callCount).toBe(1);
expect(mocks.runServerlessFunction.calls[0]).toEqual([
  {
    name: 'myFunction',
    parameters: { param1: 'value1' },
  },
]);
```

You can also use `willCall()` to provide custom logic:

```tsx theme={null}
mocks.runServerlessFunction.willCall(async ({ name, parameters }) => {
  if (name === 'myFunction') {
    return {
      status: 'SUCCESS',
      response: { result: parameters.param1.toUpperCase() },
    };
  }
  return {
    status: 'ERROR',
    error: 'Function not found',
  };
});
```

## Advanced mock features

Since mocks are built on [tinyspy](https://github.com/tinylibs/tinyspy), they expose additional properties and methods for advanced testing scenarios:

### Spy properties

All mocks provide the following properties to inspect how the function was called:

* `called`: boolean indicating if the function was called
* `callCount`: number of times the function was called
* `calls`: array of argument arrays for each call
* `results`: array of results for each call (in format `['ok', value]` or `['error', error]`)
* `returns`: array of return values for each successful call

```tsx theme={null}
import { createRenderer } from '@hubspot/ui-extensions/testing';
import { useCrmProperties } from '@hubspot/ui-extensions/crm';
import { Text } from '@hubspot/ui-extensions';

function MyComponent() {
  const { properties } = useCrmProperties(['firstname', 'lastname']);
  return <Text>{properties.firstname}</Text>;
}

const { render, mocks } = createRenderer('crm.record.tab');
render(<MyComponent />);

// Inspect how the hook was called
expect(mocks.useCrmProperties.called).toBe(true);
expect(mocks.useCrmProperties.callCount).toBe(1);
expect(mocks.useCrmProperties.calls).toEqual([[['firstname', 'lastname']]]);
expect(mocks.useCrmProperties.returns[0]).toMatchObject({
  properties: { firstname: 'fake_firstname', lastname: 'fake_lastname' },
  error: null,
  isLoading: false,
});
```

### Resetting mocks

Use the `reset()` method to clear all call history while keeping the mock implementation. This is useful when you want to reuse a mock across multiple test cases:

```tsx theme={null}
const { render, mocks } = createRenderer('crm.record.tab');

render(<MyComponent />);
expect(mocks.useCrmProperties.callCount).toBe(1);

// Clear call history
mocks.useCrmProperties.reset();
expect(mocks.useCrmProperties.callCount).toBe(0);
expect(mocks.useCrmProperties.called).toBe(false);
expect(mocks.useCrmProperties.calls).toEqual([]);

// Mock implementation still works for future calls
render(<MyComponent />);
expect(mocks.useCrmProperties.callCount).toBe(1);
```

### Restoring original implementation

Use the `restore()` method to restore the mock to its default mocked implementation. This is useful when you've customized a mock with `willCall()` and want to return to the default behavior:

```tsx theme={null}
const { render, mocks, find } = createRenderer('crm.record.tab');

// Provide a custom implementation
mocks.useCrmProperties.willCall(() => ({
  properties: { firstname: 'Custom' },
  error: null,
  isLoading: false,
}));

render(<MyComponent />);
expect(find(Text).text).toEqual('First name: Custom');

// Restore to default mock behavior
mocks.useCrmProperties.restore();

// Now it uses the default fake data again
render(<MyComponent />);
expect(find(Text).text).toEqual('First name: fake_firstname');
```

For more advanced usage and features, see the [tinyspy documentation](https://github.com/tinylibs/tinyspy).
