Create unit tests
In Dynatrace Apps, you can write unit tests using Jest. This guide teaches you how 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.
Install dependencies
First, you need to install the required dependencies:
npm i --save-dev jest ts-node ts-jest @types/jest jest-environment-jsdom @testing-library/jest-dom @testing-library/react @testing-library/user-event
If you're using React 17 or lower, use the following command to install the required dependencies:
npm i --save-dev jest ts-node ts-jest @types/jest jest-environment-jsdom @testing-library/jest-dom @testing-library/react@^12.1.5 @testing-library/user-event
jest
: Jest is a JavaScript testing framework.ts-node
: Jest needs it if you want to use TypeScript for configuration, such asjest.config.ts
.ts-jest
: A Jest transformer that allows you to test your TypeScript code. You'll need this as you'll write Dynatrace Apps in Typescript.@types/jest
: Typescript types for Jest.jest-environment-jsdom
: A Jest environment where the UI tests will run.@testing-library/jest-dom
: Provides a set of custom matchers that you can use to extend Jest. Think of a group of methods that already have most of the things you need to test UI components.@testing-library/react
: A required dependency for testing Strato components.@testing-library/user-event
: A required dependency for testing Strato components.
Ensure that versions for jest
and jest-environment-jsdom
are the same.
Add types
To get the type information in your Jest tests, you need to add Jest types in the tsconfig.json
in the types
array as follows:
{
"compilerOptions": {
...
"types": [
...,
"@types/jest", "@testing-library/jest-dom"
],
},
}
Create config
You write Dynatrace Apps in TypeScript, which Jest can't understand by default. Dynatrace Apps use the Strato design system, which requires custom configurations to be testable. To support both, you'll need to create a jest.config.ts
file in the root directory with the following code:
import type { Config } from '@jest/types';
import { stratoPreset } from '@dynatrace/strato-components-preview/testing/jest';
const config: Config.InitialOptions = {
displayName: 'ui',
testEnvironment: 'jsdom',
roots: ['<rootDir>/src'],
setupFiles: ['@dynatrace/strato-components-preview/testing'],
transform: {
'^.+\\.(t|j)sx?$': ['ts-jest', { isolatedModules: true }],
},
...stratoPreset,
};
export default config;
Let's understand the config.
- displayName: The
displayName
will appear in UI when running the test. - testEnvironment: The environment you use for testing. In this case, you need
jsdom
to have a browser-like environment for testing. - roots: Root of the project from where Jest can start looking for tests.
- setupFiles: A list of modules you want to run before running each test file. They're executed before a test framework like
@testing-library
is loaded. In this case, you're using a setup file from the Strato design system required to test code written using Strato components. - transform: The transform property specifies how Jest should transform the files before running them. In this case,
.ts
,.js
,.tsx
, and.jsx
will be transformed usingts-jest
. - stratoPreset: This is a set of default configurations required if you're using
Strato
components. It also usesmoduleNameMappers
, which maps dependencies correctly for Jest.
Additional setup files
Depending on the features you're using, you'll need more setup files. Here's the list of SDKs with their setup files imports that you'd need to add to the setupFiles
array in your jest.config.ts
file:
@dynatrace-sdk/navigation/testing
@dynatrace-sdk/user-preferences/testing
@dynatrace-sdk/app-environment/testing
Handling style imports
Jest runs your code as JavaScript. When you import styles in your React component, Jest will try to run them as JavaScript and fail. To fix this issue, you can tell Jest to mock the styles in jest.config.ts
as follows:
{
...
transform: {
'.(css|scss|sass|less)$': '<rootDir>/style-mock.ts'
},
}
Add the following content in style-mock.ts
:
module.exports = {
process() {
return { code: 'module.exports = {};' };
},
};
First UI unit test
To write your first UI unit test, create a file First.test.tsx
in the src
directory with the following content:
// imports
import React from 'react';
import { render, screen } from '@dynatrace/strato-components-preview/testing';
// component
import { Heading } from '@dynatrace/strato-components/typography';
const TestHeading = ({ textValue }) => {
return <Heading level={1}>{textValue}</Heading>;
};
// test
describe('Heading component', () => {
test('should render the Unit test on screen', () => {
render(<TestHeading textValue="Unit test" />);
expect(screen.getByText('Unit test')).toBeInTheDocument();
});
});
Let's understand the above test.
First, you have import statements. Besides importing React, you have the render
and screen
methods from the Strato design system that allow you to render a component and then create a test to see what's on the screen. You also have an import from @testing-library
that extends the expect
method of jest
to allow you to use functions like toBeInTheDocument
.
Then, you have a component named Heading
that you're testing in the following test. In real applications, this wouldn't be part of the test code. However, for simplicity in this guide, it's added to the test file.
You have a test suite using the describe and test functions. These functions are part of Jest. In the test, you're using the Heading
component from the Strato design system using Unit test
as the heading value and expecting whether the Unit test value is printed on the screen.
You'd need to import @testing-library/jest-dom
in all your tests to use functions like toBeInTheDocument
. However, you can use the setupFilesAfterEnv
property of the Jest config to import it for all tests by default as follows:
{
...
setupFilesAfterEnv: [`<rootDir>/src/jest-setup.ts`]
}
And then import the @testing-library/jest-dom
in jest-setup.ts
file as following:
import '@testing-library/jest-dom';
You don't need to import this in any of your tests.
Running UI tests
To run the tests, first, you need to create an npm script in package.json
as follows:
{
"scripts": {
"test:ui": "jest"
}
}
Now you can run the following command to run the tests:
npm run test:ui
And you'll be greeted by the following messages:
> jest
PASS ui src/First.test.tsx
Heading component
√ should render the Unit test on screen (23 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 4.311 s
Ran all test suites.
If you're using styled-components
, you might get an error like ResizeObserver not found
. To fix this issue, you'd need to mock ResizeObserver
in jest-setup.ts
as follows:
import '@testing-library/jest-dom';
global.ResizeObserver = jest.fn().mockImplementation(() => ({
observe: jest.fn(),
unobserve: jest.fn(),
disconnect: jest.fn(),
}));
Strato design system setup
and clear
functions
The Strato design system also provides two helper functions, setup
and clear
, for writing tests. By calling the setup
and the clear
functions, you can initialize and clear a set of mocks, which can be helpful for your tests as follows:
import { setup, clear } from '@dynatrace/strato-components-preview/testing';
setup();
// your test code here
clear();
Only use the setup
and clear
functions if the Strato components don't behave as they should.
Test your app functions
When you run the generate function
command, the Dynatrace app toolkit automatically creates a test file. This happens for every function you generate. You also get the jest.config.js
file created in your app's api
directory.
Your unit tests have access to all available APIs in the Dynatrace JavaScript Runtime.
Mock your fetch functions
If you're using fetch
to call third party data sources from within your app functions, you need to overwrite its implementation in your unit tests.
The generated test file already contains the following code snippet, which overwrites the global fetch
function with the mocked fetchMock
function:
const fetchMock = jest.fn();
globalThis.fetch = fetchMock;
By using the mockImplementation
or mockImplementationOnce
function, you can mock the behavior of your actual fetch call.
fetchMock.mockImplementationOnce(() => {
Promise.resolve({
ok: true,
status: 200,
json: () => Promise.resolve({ id: 3, firstname: 'John', lastname: 'Doe' }),
});
});
If you have UI and app functions tests, you can use the projects
property to combine UI and app functions configurations. Take a look at the projects property