Skip to main content
App pages support routing and navigation, which requires special consideration when writing tests. This guide shows you how to test app pages using the UI extensions SDK testing utilities with mocked routing state, paths, and parameters.

Create mocks

When you create a renderer with createRenderer('pages'), the following mocks are available:
const { mocks, render, findByTestId } = createRenderer('pages');

// Mock the current route (controls usePageRoute)
mocks.router.setCurrentRoute({
  path: '/contacts/:contactId',
  params: { contactId: '123', tab: 'activity' }
});

// Mock navigation action
mocks.actions.navigateToPage; // FunctionSpy for testing navigation calls
Keep the following in mind as you mock out your components and their behavior:
  • Use 'pages' as the extension point location when creating a renderer
  • Mock the current route using mocks.router.setCurrentRoute() with path and params
  • Always render the full <AppPages /> component with <PageRoutes>, not individual page components
  • Use findByTestId for reliable component querying in tests

Testing app pages with routing and parameters

This example demonstrates the most common testing scenario: rendering an app with routing and testing that the correct page component displays with the right parameters.
import { createRenderer } from '@hubspot/ui-extensions/testing';
import { Text, Alert } from '@hubspot/ui-extensions';
import { createPageRouter, PageRoutes, usePageRoute } from '@hubspot/ui-extensions/pages';

// Define page components
const HomePage = () => <Text testId="home-page">Home Page</Text>;

const ContactDetailsPage = () => {
  const { path, params } = usePageRoute();
  const { contactId, tab } = params;
  
  if (!contactId) {
    return <Alert title="Error" testId="error-alert">Missing contact ID</Alert>;
  }
  
  return (
    <>
      <Text testId="contact-id">Contact ID: {contactId}</Text>
      <Text testId="active-tab">Tab: {tab || 'overview'}</Text>
      <Text testId="current-path">Path: {path}</Text>
    </>
  );
};

const NotFoundPage = () => <Text testId="404-page">Page Not Found</Text>;

// Define the app structure with routes
const PageRouter = createPageRouter(
  <PageRoutes>
    <PageRoutes.IndexRoute component={HomePage} />
    <PageRoutes.Route path="/contacts/:contactId" component={ContactDetailsPage} />
    <PageRoutes.AnyRoute component={NotFoundPage} />
  </PageRoutes>
);

const AppPages = () => <PageRouter />;

// Test: home page renders
test('renders home page for root path', () => {
  const { mocks, render, findByTestId } = createRenderer('pages');
  
  mocks.router.setCurrentRoute({
    path: '/',
    params: {}
  });
  
  render(<AppPages />);
  
  expect(findByTestId(Text, 'home-page')).toBeDefined();
});

// Test: page with path parameter
test('renders contact page with path parameter', () => {
  const { mocks, render, findByTestId } = createRenderer('pages');
  
  mocks.router.setCurrentRoute({
    path: '/contacts/:contactId',
    params: { contactId: '123' }
  });
  
  render(<AppPages />);
  
  expect(findByTestId(Text, 'contact-id').text).toEqual('Contact ID: 123');
  expect(findByTestId(Text, 'current-path').text).toEqual('Path: /contacts/:contactId');
});

// Test: page with path and query parameters
test('renders contact page with path and query parameters', () => {
  const { mocks, render, findByTestId } = createRenderer('pages');
  
  mocks.router.setCurrentRoute({
    path: '/contacts/:contactId',
    params: { 
      contactId: '456', 
      tab: 'activity' 
    }
  });
  
  render(<AppPages />);
  
  expect(findByTestId(Text, 'contact-id').text).toEqual('Contact ID: 456');
  expect(findByTestId(Text, 'active-tab').text).toEqual('Tab: activity');
});

// Test: error state when parameter is missing
test('shows error when contact ID is missing', () => {
  const { mocks, render, findByTestId } = createRenderer('pages');
  
  mocks.router.setCurrentRoute({
    path: '/contacts/:contactId',
    params: { contactId: '' }
  });
  
  render(<AppPages />);
  
  expect(findByTestId(Alert, 'error-alert')).toBeDefined();
});

// Test: 404 page for unknown route
test('renders 404 page for unknown path', () => {
  const { mocks, render, findByTestId } = createRenderer('pages');
  
  mocks.router.setCurrentRoute({
    path: '/unknown-path',
    params: {}
  });
  
  render(<AppPages />);
  
  expect(findByTestId(Text, '404-page')).toBeDefined();
});
Always render the full <AppPages /> component with <PageRoutes> in your tests. This ensures you’re testing the actual routing behavior, not just individual page components in isolation.

Testing navigation

The following code blocks demonstrate how to test that your components call navigateToPage with the correct arguments:
import { Button } from '@hubspot/ui-extensions';

const GoToContactButton = ({ context }) => {
  const handleClick = () => {
    context.actions.navigateToPage({
      pagePath: '/contacts/:contactId',
      params: { contactId: '123', tab: 'activity' }
    });
  };
  
  return (
    <Button onClick={handleClick} testId="nav-button">
      View Contact
    </Button>
  );
};

test('navigates when button is clicked', () => {
  const { mocks, render, findByTestId } = createRenderer('pages');
  
  render(<GoToContactButton />);
  
  // Trigger navigation
  findByTestId(Button, 'nav-button').trigger('onClick');
  
  // Assert navigation was called
  expect(mocks.actions.navigateToPage.called).toBe(true);
  expect(mocks.actions.navigateToPage.callCount).toBe(1);
  
  // Assert navigation arguments
  const [navArgs] = mocks.actions.navigateToPage.calls[0];
  expect(navArgs).toEqual({
    pagePath: '/contacts/:contactId',
    params: { contactId: '123', tab: 'activity' }
  });
});

Testing route IDs

When your components use routeId from usePageRoute(), include the routeId field in your setCurrentRoute call to mock the matched route’s ID:
import { createRenderer } from '@hubspot/ui-extensions/testing';
import { Text } from '@hubspot/ui-extensions';
import { createPageRouter, PageRoutes, PageBreadcrumbs, PageTitle, PageLink, usePageRoute } from '@hubspot/ui-extensions/pages';

function AppLayout({ children }: { children: ReactNode }) {
  const { routeId } = usePageRoute();

  const titles: Record<string, string> = {
    home: 'Home',
    support: 'Support',
  };

  return (
    <>
      <PageTitle testId="page-title">{titles[routeId]}</PageTitle>
      {children}
    </>
  );
}

const PageRouter = createPageRouter(
  <PageRoutes layoutComponent={AppLayout}>
    <PageRoutes.IndexRoute id="home" component={HomePage} />
    <PageRoutes.Route id="support" path="/support" component={SupportPage} />
  </PageRoutes>
);

const AppPages = () => <PageRouter />;

test('layout renders correct title based on routeId', () => {
  const { mocks, render, findByTestId } = createRenderer('pages');

  mocks.router.setCurrentRoute({
    path: '/support',
    routeId: 'support',
    params: {}
  });

  render(<AppPages />);

  expect(findByTestId(PageTitle, 'page-title').children).toEqual('Support');
});

Testing recommendations and examples

The sections below provide guidance on how to test your app pages and their associated functionality:

Always render the full app structure

Render <AppPages /> with <PageRoutes>, not individual page components:
// Good - tests actual routing
const PageRouter = createPageRouter(
  <PageRoutes>
    <PageRoutes.IndexRoute component={HomePage} />
    <PageRoutes.Route path="/contacts/:contactId" component={ContactDetails} />
  </PageRoutes>
);
const AppPages = () => <PageRouter />;

test('renders contact page', () => {
  const { mocks, render } = createRenderer('pages');
  mocks.router.setCurrentRoute({
    path: '/contacts/:contactId',
    params: { contactId: '123' }
  });
  render(<AppPages />);
  // ...
});

// Bad - bypasses routing
test('renders contact page', () => {
  const { mocks, render } = createRenderer('pages');
  mocks.router.setCurrentRoute({
    path: '/contacts/:contactId',
    params: { contactId: '123' }
  });
  render(<ContactDetails />);  // Doesn't test routing!
  // ...
});

Use findByTestId for reliable querying

Add testId props to components for stable, maintainable tests:
const ContactPage = () => {
  const { params } = usePageRoute();
  return (
    <>
      <Text testId="contact-id">Contact: {params.contactId}</Text>
      <Text testId="status">Active</Text>
    </>
  );
};

test('displays contact', () => {
  const { mocks, render, findByTestId } = createRenderer('pages');
  mocks.router.setCurrentRoute({
    path: '/contacts/:contactId',
    params: { contactId: '123' }
  });
  render(<AppPages />);
  
  expect(findByTestId(Text, 'contact-id').text).toEqual('Contact: 123');
});

Mock only what you need

Only mock the parameters your component actually uses:
// Good - minimal mocking
test('displays contact name', () => {
  const { mocks, render } = createRenderer('pages');
  mocks.router.setCurrentRoute({
    path: '/contacts/:contactId',
    params: { contactId: '123' }
  });
  render(<AppPages />);
  // ...
});

// Unnecessary - extra params
test('displays contact name', () => {
  const { mocks, render } = createRenderer('pages');
  mocks.router.setCurrentRoute({
    path: '/contacts/:contactId',
    params: { 
      contactId: '123',
      dealId: '456',      // Unused
      companyId: '789'    // Unused
    }
  });
  render(<AppPages />);
  // ...
});

Test error states

Test how your pages handle missing or invalid parameters:
const ContactPage = () => {
  const { params } = usePageRoute();
  
  if (!params.contactId) {
    return <Alert title="Error" testId="error">Missing contact ID</Alert>;
  }
  
  return <Text testId="contact">Contact: {params.contactId}</Text>;
};

test('shows error when contact ID is missing', () => {
  const { mocks, render, findByTestId } = createRenderer('pages');
  
  mocks.router.setCurrentRoute({
    path: '/contacts',
    params: {}
  });
  
  render(<AppPages />);
  
  expect(findByTestId(Alert, 'error')).toBeDefined();
});

Remember parameters are strings

All parameters are strings. Convert types when needed:
const AnalyticsPage = () => {
  const { params } = usePageRoute();
  
  // Convert string params to proper types
  const countNumber = parseInt(params.count || '0', 10);
  const enabledBoolean = params.enabled === 'true';
  
  return (
    <>
      <Text testId="count">Count: {countNumber}</Text>
      <Text testId="enabled">Enabled: {enabledBoolean ? 'Yes' : 'No'}</Text>
    </>
  );
};

test('converts parameter types', () => {
  const { mocks, render, findByTestId } = createRenderer('pages');
  
  mocks.router.setCurrentRoute({
    path: '/analytics',
    params: { count: '42', enabled: 'true' }
  });
  
  render(<AppPages />);
  
  expect(findByTestId(Text, 'count').text).toEqual('Count: 42');
  expect(findByTestId(Text, 'enabled').text).toEqual('Enabled: Yes');
});
Check out the following guides for additional guidance on testing and navigating between your pages:
Last modified on April 14, 2026