---
sourceDocument: Australia Build or modify applications
sourceDocumentLink: https://www.servicenow.com/docs/r/australia/application-development

 Release :

    - australia

ft:locale :

    - en-US

ft:publication_title :

    - Australia Build or modify applications

ft:clusterId :

    - cadev

bundleId :

    - cadev

workflow :

    - Development, Data, and Analytics


---

# Test an app locally

# Test an app locally {#ariaid-title1}

Release version: Australia  
Updated September 29, 2026  
![](https://www.servicenow.com/docs/portal-asset/ico-clock) 3 minutes to read  
Testing a page locally with Vitest catches loader and rendering failures before you deploy. Separate unit tests cover your data-fetching logic, and separate render tests cover the HTML your component produces.

## Before you begin

* A page with a `static async loader(ctx)` method and a `render()` method. To learn how a page declares both, see [Create a page](https://www.servicenow.com/docs/Il1iHhhSWeeZJpVYzRg6wQ "Add a page to a Lux app. A page is a Lit web component that Lux registers as a route based on where you put its file.").
* Vitest configured, which is present by default in an app scaffolded with `now-sdk init`.
* Role required: admin
{#test-an-app-locally__ul_xj5_zkh_tkc}

## About this task

Lux uses Vitest with a two-tier strategy: loader unit tests verify your data-fetching logic, and server-side rendering (SSR) render tests verify the HTML your component produces. They live in separate
files; mixing them causes `vi.mock()` conflicts, because loader tests mock Lit entirely while render tests require real Lit and a DOM shim. To access the test runner's reference material, see the [Vitest documentation](https://vitest.dev/).

## Procedure

1. Create the test files.  

       my-app/
       ├── pages/
       │   └── risk/page.js
       └── test/
           ├── risk-page-loader.test.js   ← Tier 1: loader tests
           └── risk-page.test.js          ← Tier 2: SSR render tests

   Test files live in test/ directories alongside src/.
2. Write loader unit tests.  
   Mock Lit and `fetch`, then assert on your loader's behavior, not on the mock.

       import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest';

       vi.mock('lit', () => ({isServer: true}));
       vi.mock('lit/decorators.js', () => ({customElement: () => cls => cls}));

       const mockGetHeaders = vi.fn(() => ({'Content-Type': 'application/json'}));
       vi.mock('@servicenow/aiux-components-core', () => ({
         AIUXElement: class {},
         getHeaders: (...args) => mockGetHeaders(...args)
       }));

       const {default: MyPage} = await import('../pages/risk/page.js');

       describe('MyPage.loader', () => {
         it('calls the correct API endpoint', async () => {
           globalThis.fetch = vi.fn(async () => ({json: async () => ({result: []})}));
           await MyPage.loader(mockCtx);
           expect(globalThis.fetch).toHaveBeenCalledTimes(1);
         });
       });

   Assert on the fetch URL and method, that `getHeaders(ctx)` was forwarded, the return value's shape, error handling, and edge cases such as missing fields and empty arrays.
3. Copy the SSR test helper.  
   SSR render tests require real Lit and a DOM shim. Copy test/ssr-utils.js --- about 50 lines --- from the framework monorepo into your app's test/ directory.
4. Write SSR render tests, and render against mock loader data:  

       import {html} from 'lit';
       import {setupLitSSR, renderToString, clean} from './helpers/ssr-utils.js';

       let render;
       beforeAll(async () => {
         ({render} = await setupLitSSR());
         await import('../pages/risk/page.js');
       });

       it('renders the risk list', async () => {
         globalThis.__COMPONENT_DATA__ = {risks: [{number: 'RSK0001'}], error: null};
         const result = render(html`<my-risk-page></my-risk-page>`);
         const output = clean(await renderToString(result));
         expect(output).toContain('RSK0001');
       });

   During real SSR, the framework sets `globalThis.__COMPONENT_DATA__` with your loader's return value before rendering, and `AIUXElement` reads it in its constructor. In tests, set it
   directly.
5. Run the tests.  
   Run everything, one package, one file, or one test by name.

       pnpm test                                                  # all tests
       pnpm exec vitest run test/risk-page-loader.test.js         # one file
       pnpm exec vitest run -t "calls the correct API endpoint"    # by test name
       pnpm exec vitest watch test/                               # watch mode
       pnpm test:coverage                                         # coverage report

   To run a single package's tests, change into its directory first and run `pnpm test` there.
6. Run lint and type-check before you build.  

       pnpm lint
       pnpm typecheck

## Other test types {#test-an-app-locally__test-an-app-locally-other-test-types}

The loader and SSR render tests described earlier are page-level tests running in Node. Two other test types support client-side verification. All three are provided in the following table.
{#test-an-app-locally__table-test-an-app-locally-test-types__entry__4}

| Type | File pattern | Command | Runs in |
|-|-|-|-|
| Unit | `*.test.js` | `pnpm test` | Node environment |
| Browser-mode unit | `*.browser.js` | `pnpm test:browser` | Vitest browser mode |
| Smoke | `*.smoke.js` | `pnpm test:smoke` | Real Chromium, driven by Playwright --- verifies a widget mounts and hydrates |
[Table 1. Test types]

{#test-an-app-locally__table-test-an-app-locally-test-types}

Add `pnpm test:smoke:headed` to run smoke tests with a visible browser window.

## Don't test the mock {#test-an-app-locally__test-an-app-locally-dont-test-the-mock}

A common mistake is mocking a call to return `X`, then asserting the result is `X`. That verifies the mock, not your code. Mock the dependency, which is the network call, and assert on the behavior, which is what your loader does with the response.

## What to do next

After your tests pass, move on to [Deploy to an instance](https://www.servicenow.com/docs/XNdWsS~JGOTDQC7m9tRENg "Getting a change onto a ServiceNow instance is a three-step pipeline: build the app, deploy its records, then verify the service and the app. The same pipeline runs from the command line, from Lux Lab, or ServiceNow Lux Lab for VS Code."), `pnpm test` is the first item on that topic's ship checklist. For diagnosing a failure you can't explain from a test alone, see . For the build-time and CI checks that run beyond what you can trigger locally, see .

