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'sexpect.@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:
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—exposesdescribe,it,expect,vi, and similar functions as globals so test files don't need to import them explicitly.environment—the test environment. Usejsdomto 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 ownvitest.setup.tsis 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:
| Package | What it mocks | When you need it |
|---|---|---|
@dynatrace-sdk/app-environment/testing | globalThis.dtRuntime.appEnvironment — app ID, environment URL, user details | Any component that calls getAppId(), getEnvironmentUrl(), etc. |
@dynatrace-sdk/navigation/testing | The navigation SDK | Components or hooks that navigate between app pages |
@dynatrace-sdk/user-preferences/testing | User preferences, including language | Components that read locale or user settings |
@dynatrace-sdk/error-handlers/testing | Error handler registration | Components that register or trigger error handlers |
Add the packages your app depends on to setupFiles in your 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',
],
},
Vitest support in these packages requires the following minimum versions:
| Package | Minimum version |
|---|---|
@dynatrace-sdk/app-environment | 1.1.5 |
@dynatrace-sdk/user-preferences | 1.1.5 |
@dynatrace-sdk/navigation | 2.3.1 |
@dynatrace-sdk/error-handlers | 1.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:
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.
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.
// 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:
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
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.
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.