Skip to main content

Setup

Steps to create a test

1. Annotate Key Elements with data-testid

Mark the key components with a data-testid attribute. These elements are the ones you will probe for values, states, or perform interactions like clicks or selections.

Example

// Use data-testid on the span for easily retrieving the displayed name.
<Typography variant="h1">
Hello <span data-testid="person-name">{name}</span>
</Typography>

// Annotate the AutoComplete component to set its selection.
<AutoComplete data-testid="favorite-color-value" choices={choices} />

// A MUI button can be probed for its state (e.g., "disabled") or clicked.
<MuiButton data-testid="submit" disabled={disabled}>
Submit
</MuiButton>

2. Define Scene Parts

A ScenePart describes how to locate a key component and which driver to use for interacting with it. For instance, the example above can be mapped as follows:

import { HTMLComponentDriver } from '@atomic-testing/component-driver-html';
import { ButtonDriver, AutoCompleteDriver } from '@atomic-testing/component-driver-mui-v6';
import { byDataTestId, ScenePart } from '@atomic-testing/core';

const parts = {
personDisplay: {
locator: byDataTestId('person-name'),
driver: HTMLComponentDriver,
},
favoriteColorInput: {
locator: byDataTestId('favorite-color-value'),
driver: AutoCompleteDriver,
},
submitButton: {
locator: byDataTestId('submit'),
driver: ButtonDriver,
},
} satisfies ScenePart; // Using "satisfies ScenePart" improves TypeScript type-checking.

3. Render the Component with TestEngine

The TestEngine is your entry point for interacting with a component. Use the createTestEngineForComponent() method to instantiate the engine. Assign it in beforeEach and clean it up in afterEach to prevent cross-test contamination.

import { TestEngine } from '@atomic-testing/core';
import { createTestEngineForComponent } from '@/shared/utils/ui-test-util';

let testEngine: TestEngine<typeof parts>; // Type declaration ensures precise type checking.

beforeEach(() => {
testEngine = createTestEngineForComponent(<MyComponent prop1={value1} />, parts);
});

afterEach(async () => {
await testEngine.cleanUp();
});

After instantiation, you can access your declared parts through the engine’s parts property:

await testEngine.parts.personDisplay.text(); // Retrieves plain text from the personDisplay part.

await testEngine.parts.favoriteColorInput.setValue('Black');
// Sets the auto-complete component to "Black" using the AutoCompleteDriver's method.

await testEngine.parts.submitButton.isDisabled(); // Checks if the submit button is disabled.

await testEngine.parts.submitButton.click(); // Simulates a click on the submit button.

4. Writing Your Tests

Write tests as you normally would, keeping in mind that most UI interactions are asynchronous. Here’s an example of how a test flow might look:

it(`should display the person's name as "John Doe"`, async () => {
expect(await testEngine.parts.personDisplay.text()).toEqual('John Doe');
});

it(`should enable the submit button once a color is chosen`, async () => {
await testEngine.parts.favoriteColorInput.setValue('Black');
expect(await testEngine.parts.submitButton.isDisabled()).toEqual(false);
});

Demo

The following demo illustrates the complete setup of a login form using Atomic Testing. It includes the test setup, component rendering, and interaction with the UI elements.

Sample Login Form