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, 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.
When you create a renderer with createRenderer(extensionPointLocation), the testing SDK automatically creates appropriate mocks for the Extension Point API based on the location you specify. This includes:
runServerlessFunction(): A mock implementation for calling serverless functions
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.
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
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');
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:
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 statemocks.useCrmProperties.nextResult({ properties: {}, error: new Error('Something went wrong!'), isLoading: false,});render(<MyComponent />);expect(find(Text).text).toEqual('Something went wrong!');
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:
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.
The context object is automatically populated with fake data appropriate for the extension point location. You can access and customize it via mocks.context:
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:
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:
const { render, mocks } = createRenderer('crm.record.tab');render(<MyComponent />);expect(mocks.useCrmProperties.callCount).toBe(1);// Clear call historymocks.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 callsrender(<MyComponent />);expect(mocks.useCrmProperties.callCount).toBe(1);
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:
const { render, mocks, find } = createRenderer('crm.record.tab');// Provide a custom implementationmocks.useCrmProperties.willCall(() => ({ properties: { firstname: 'Custom' }, error: null, isLoading: false,}));render(<MyComponent />);expect(find(Text).text).toEqual('First name: Custom');// Restore to default mock behaviormocks.useCrmProperties.restore();// Now it uses the default fake data againrender(<MyComponent />);expect(find(Text).text).toEqual('First name: fake_firstname');