> ## 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: 440202a1-1269-41e0-8495-8d56fe3cabe9
---

# Testing app pages

> Learn how to test app pages with mocked routing, paths, and parameters.

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:

```tsx theme={null}
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.

```tsx theme={null}
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();
});
```

<Note>
  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.
</Note>

## Testing navigation

The following code blocks demonstrate how to test that your components call `navigateToPage` with the correct arguments:

```tsx theme={null}
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:

```tsx theme={null}
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:

```tsx theme={null}
// 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:

```tsx theme={null}
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:

```tsx theme={null}
// 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:

```tsx theme={null}
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:

```tsx theme={null}
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');
});
```

## Related resources

Check out the following guides for additional guidance on testing and navigating between your pages:

* [Testing overview](/apps/developer-platform/add-features/ui-extensions/tools/testing/overview)
* [Testing reference](/apps/developer-platform/add-features/ui-extensions/tools/testing/reference)
* [Mocking](/apps/developer-platform/add-features/ui-extensions/tools/testing/mocking)
* [Page routing](/apps/developer-platform/add-features/ui-extensions/extension-points/app-pages/page-routing)
* [Page linking and navigation](/apps/developer-platform/add-features/ui-extensions/extension-points/app-pages/page-linking)
