Skip to main content

Testing philosophy

Jutro approach to testing

Here at Jutro, we recommend you follow the testing principles encouraged by Testing Library. In short:

  • Interact with your app the same way as your users
  • Test your application, not your implementation details
  • Use selectors based on accessibility, not your knowledge of application structure

The @jutro/test package also provides a number of useful helpers that make following the principles easier.

Additional reading

Principles in practice

Interact

To interact with your app the same way as your users, select elements by attributes the user interacts with, like labels or role. Avoid using implementation details, like IDs and classes.

Do

Select a button by text.

Don't

Select a button by ID, or position in the DOM tree.

To ensure that your app is properly translated, you would not interact with the screen text but with a translation from your messages file. To give a more realistic example:

import { getTranslation } from '@jutro/test';
import { messages } from '../MyComponent.messages';

const theButton = screen.getByRole('button', {
name: getTranslation(messages.awesomeButton),
});

You could write a component test like so:

import { render, screen } from '@jutro/test';

test('has correct welcome text', () => {
render(
<Welcome
firstName="John"
lastName="Doe"
/>
);
expect(
screen.getByRole('heading', { name: 'Welcome, John Doe' })
).toBeInTheDocument();
});

If you want more examples, we've put them in the More interaction examples section.

Application

To test your application, not your implementation details, focus on the result in the application, rather than application internal state changes.

Do

Test whether the profile menu button is displayed when the user is logged into the application (an e2e test).

Don't

Write a unit test which sets Avatar.isAuthenticated to true and then checks if Avatar.isDisplaying is also set to true.

test('Verify that avatar exists in the application header after login', async (t) => {
await t
.expect(
queryByRole('button', {
name: getTranslation('User Profile'),
}).exists
)
.ok();
});

Accessibility

To use selectors based on accessibility, rely on aria-role and other ARIA attributes. That way you are testing what your application renders for the user without the details of how it is rendered there.

Do

Check the rendered HTML for a button with the role of "tab", whose ARIA "selected" attribute is set to "true".

Don't

Select that div which renders a tab button and check if it has a class which includes the word "active".

If rendered correctly, the HTML for a set of tabs is:

<div role="tablist">
<button
role="tab"
aria-selected="false"
class="tab">
Pending claims
</button>
<button
role="tab"
aria-selected="true"
class="active tab">
Accepted claims
</button>
<button
role="tab"
aria-selected="false"
class="tab">
Rejected claims
</button>
</div>

In that case, your test can select:

const activeTab = screen.getByRole('tab', { selected: true });

For more details, see Accessibility testing.

Use component IDs only as a last resort

If you find yourself in the rare situation where user-centered approach and accessibility are not enough, you can add the data-testid attribute. For example, this is a good approach when you want to check if a page renders and you want to select the whole div for something like a "Welcome" page which has no specific functionality.

Render your component like this:

<div data-testid="home-page">{children}</div>

And select it like this:

const homePage = screen.getByTestId('home-page');

Automatically generated IDs

Each field component on a page gets a unique integer added at the end of the ID. This ensures every ID is unique. The same integer is added to labels which refer to the ID, so they don't lose the connection.

For example, if you specify the ID to be soup, the HTML component has an ID like soup_0, soup_1, and so on.

To test by using the ID in a selector, you have the following options:

Test ID

In the rendered HTML, each field gets an extra data-testid attribute which is the same as the ID without the integer at the end. That means the value may be duplicated for some components on your page.

You can use the testId prop to set a custom value for the data-testid attribute in HTML.

{
"id": "soup",
"type": "field",
"component": "Input",
"componentProps": {
"testId": "chowder"
}
}

The resulting HTML:

<input
id="soup_0"
data-testid="chowder" />

Wildcard

Use a wildcard in the selector, like input[id*="soup"].

Switch off Automatically generating IDs

You can set your app to not generate unique IDs. In src/config/config.json, set:

src/config/config.json
"generateUniqueId": false,

By default, this option is set to true. If you set it to false, you may end up with duplicated IDs on some pages.