Skip to main content

Create unit tests with Vitest

  • New
  • How-to guide
  • 7-min read

In Dynatrace Apps, write unit tests with Vitest. Vitest is a Vite-native test framework that supports TypeScript without an additional transformer, making it a fast alternative to Jest for apps already using Vite. Use this guide to write unit tests for React components and app functions.

Test your UI

Testing your app's UI components helps you recognize errors early on. The Strato Design System offers features to help you write unit tests for your React app with Vitest.

Install dependencies

Here are the dependencies you need to install:

  • vitest: Vitest is a Vite-native JavaScript testing framework. It handles TypeScript natively, so no separate transformer is needed.
  • jsdom: A browser-like environment for running UI tests in Node.
  • @vitejs/plugin-react: The Vite React plugin, required for JSX support in Vitest.
  • @testing-library/jest-dom: Provides a set of custom matchers (for example, toBeInTheDocument) that extend Vitest's expect.
  • @testing-library/react: A required dependency for testing Strato components.
  • @testing-library/user-event: A required dependency for testing Strato components.

To install the dependencies, run the following command:

npm i --save-dev --save-exact vitest jsdom @vitejs/plugin-react @dynatrace/strato-components-testing @testing-library/jest-dom @testing-library/react @testing-library/user-event

Create a configuration file

Create a vitest.config.ts file in the root directory of your app. Dynatrace Apps are built with the Strato Design System, which provides stratoVitestPreset to supply the necessary defaults. Merge it with your own configuration as follows:

vitest.config.ts
import { defineConfig, mergeConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
import { stratoVitestPreset } from '@dynatrace/strato-components-testing/vitest/preset';

export default mergeConfig(
stratoVitestPreset,
defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['@dynatrace/strato-components-testing/vitest/setup', './vitest.setup.ts'],
},
}),
);

Here's a description of the key settings:

  • globals: true—exposes describe, it, expect, vi, and similar functions as globals so test files don't need to import them explicitly.
  • environment—the test environment. Use jsdom to get a browser-like environment for testing React components.
  • setupFiles—modules to run before each test file. The Strato setup file installs the browser API mocks required by Strato components. Your own vitest.setup.ts is the right place to add project-level setup.
  • stratoVitestPreset—a set of default configurations required for Strato components, including module name mappers and transforms.

Additional setup files

Depending on the Dynatrace SDK you use, you may need to add more entries to setupFiles:

PackageWhat it mocksWhen you need it
@dynatrace-sdk/app-environment/testingglobalThis.dtRuntime.appEnvironment — app ID, environment URL, user detailsAny component that calls getAppId(), getEnvironmentUrl(), etc.
@dynatrace-sdk/navigation/testingThe navigation SDKComponents or hooks that navigate between app pages
@dynatrace-sdk/user-preferences/testingUser preferences, including languageComponents that read locale or user settings
@dynatrace-sdk/error-handlers/testingError handler registrationComponents that register or trigger error handlers

Add the packages your app depends on to setupFiles in your vitest.config.ts:

vitest.config.ts
test: {
setupFiles: [
'@dynatrace/strato-components-testing/vitest/setup',
'@dynatrace-sdk/app-environment/testing',
'@dynatrace-sdk/navigation/testing',
'@dynatrace-sdk/user-preferences/testing',
'@dynatrace-sdk/error-handlers/testing',
'./vitest.setup.ts',
],
},
Note

Vitest support in these packages requires the following minimum versions:

PackageMinimum version
@dynatrace-sdk/app-environment1.1.5
@dynatrace-sdk/user-preferences1.1.5
@dynatrace-sdk/navigation2.3.1
@dynatrace-sdk/error-handlers1.3.2

Older versions only initialize when typeof expect === "undefined", a condition Jest meets (it injects expect after setupFiles run) but Vitest does not. The versions above add a process.env.VITEST check so they initialize correctly in both runners.

Once listed in setupFiles, the mocks are active for the entire test file. If you need to customize a mock's return values for a specific test — for example, to simulate a different environment URL — call the mock function directly inside your test:

import { mockAppEnvironment, clearAppEnvironmentMocks } from '@dynatrace-sdk/app-environment/testing';

afterEach(() => {
clearAppEnvironmentMocks();
});

it('shows the correct environment URL', () => {
mockAppEnvironment({ getEnvironmentUrl: () => 'https://my-env.example.com' });
// ... rest of test
});

Create a setup file

Create vitest.setup.ts in the root of your project to initialize testing utilities that must run before every test file:

vitest.setup.ts
import '@testing-library/jest-dom/vitest';

// Required for React 18 act() to work correctly in Vitest.
(globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;

Importing @testing-library/jest-dom/vitest registers the custom matchers (like toBeInTheDocument) with Vitest's expect, so you don't need to import them in every test file.

Note

If you're using @testing-library/dom waitFor with vi.useFakeTimers(), add the line below to vitest.setup.ts. Without it, waitFor cannot detect Vitest fake timers and will hang until the test timeout.

vitest.setup.ts
// Make @testing-library/dom's waitFor detect Vitest fake timers.
(globalThis as Record<string, unknown>).jest = vi;

Your first UI unit test

Create a file First.test.tsx in your ui directory:

ui/First.test.tsx
import React from 'react';
import { render } from '@dynatrace/strato-components-testing/vitest';
import { screen } from '@testing-library/react';
import { Heading } from '@dynatrace/strato-components/typography';

const TestHeading = ({ textValue }: { textValue: string }) => <Heading level={1}>{textValue}</Heading>;

describe('Heading component', () => {
it('should render the Unit test on screen', () => {
render(<TestHeading textValue="Unit test" />);
expect(screen.getByText('Unit test')).toBeInTheDocument();
});
});

Because globals: true is set in the config, describe, it, and expect are available without any imports. The render function comes from @dynatrace/strato-components-testing/vitest, which wraps @testing-library/react with Strato-specific providers.

Run UI tests

Add an npm script to package.json:

{
"scripts": {
"test:unit": "vitest run"
}
}

Run the tests with:

npm run test:unit

You'll see output similar to the following:

RUN v3.x.x

✓ ui/First.test.tsx (1 test) 42ms

Test Files 1 passed (1)
Tests 1 passed (1)
Start at 12:00:00
Duration 1.23s

To run only a subset of tests, pass a file pattern directly to vitest run:

npx vitest run MyComponent
Tip

To run tests in watch mode during development, use vitest without the run subcommand:

npx vitest

Vitest will re-run affected tests automatically when files change.

Strato Design System setup functions

@dynatrace/strato-components-testing/vitest/setup (listed in setupFiles) installs the browser API mocks that Strato components require. You do not need to call any additional setup or teardown functions manually—the preset handles this automatically.

Test your app functions

For app functions (server-side logic), Vitest works without a DOM environment. Set environment: 'node' in the config for those test files, or use Vitest's environmentMatchGlobs to apply different environments per file pattern.

Mock your fetch functions

If your app functions use fetch to call third-party data sources, override it in your tests using vi.fn():

const fetchMock = vi.fn();
globalThis.fetch = fetchMock;

Use mockImplementationOnce to control what a single call returns:

fetchMock.mockImplementationOnce(() =>
Promise.resolve({
ok: true,
status: 200,
json: () => Promise.resolve({ id: 3, firstname: 'John', lastname: 'Doe' }),
}),
);

Mock modules

Use vi.mock() where you would previously use jest.mock(), and vi.fn() / vi.spyOn() in place of their jest.* equivalents:

// Mock an entire module
vi.mock('./myModule', () => ({
myFunction: vi.fn().mockReturnValue('mocked'),
}));

// Partial mock — keep real exports, override one
vi.mock('./myModule', async (importOriginal) => {
const actual = await importOriginal<typeof import('./myModule')>();
return { ...actual, myFunction: vi.fn() };
});

// Spy on a specific method
vi.spyOn(console, 'error').mockImplementation(() => {});

vi.mock() calls are automatically hoisted to the top of the file by Vitest, as they are by Jest.

Tip

If you have separate configurations for UI and app function tests, you can use Vitest's projects option to define multiple test projects within a single vitest.config.ts.

Still have questions?
Find answers in the Dynatrace Community