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
- You can learn more about these principles on the Testing Library guiding principles page.
- We also encourage you to read this article about why it's bad to test implementation details.
- Don't forget about the priority of queries!
- Afraid you'll make mistakes? Read about some common mistakes, so that you can avoid them.
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.
Select a button by text.
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.
Test whether the profile menu button is displayed when the user is logged into the application (an e2e test).
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.
Check the rendered HTML for a button with the role of "tab", whose ARIA "selected" attribute is set to "true".
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:
"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.
Was this page helpful?