This document is a starting/reference guide for writing unit tests for the UI modules within ERM.
RTL (React testing library) is a testing utility for React that has been adopted by the FOLIO community as the framework for writing Unit tests within the UI modules.
This doc is a good place to start with understanding RTL, as to what problem it essentialy solves within the UI testing world.
Jest is the test runner that react recommends that lets you access the dom and make assertions against it within your tests.
RTL/JEST setup within ERM:
- The entire setup for the RTL/jest infrastructure lies within the stripes-erm-components under the test/jest folder. (PR that set it up for more reference)
- We have chosen stripes-erm-components to place this infrastructure because all the ERM UI modules have a dependency on stripes-erm-components which lets us allow to pull in the test configuration directly into the individual ui-* modules with a very simple setup, for example in ui-agreements, which pulls in the config from the jest.config.js in erm-components
- The __mock__ folder mocks the entire underlying stripes libraries, for eg: stripes-core, stripes-smart-components, stripesConfig, stripes-connect etc. These mocks are what let us not worry about any stripes dependencies we may have while testing, and just be concerned with testing the component in isolation.
Note: Its very important all the developers understand what each of the mock is actually mocking and also understand the jest configuration within the jest.config.js file. Understanding these details will help devs debug the tests quickly specially when we hit errors/issues within the tests that are due to stripes level dependencies.
Writing RTL tests:
Before writing the RTL tests, its important to arrange where you would want to place the test file. RTL picks up test files with extension __filename__.test.js (config). Structurally its easier to group the actual component thats being tested along with the test file. Refer to the AddToBasketButton folder that has the component and its corresponding test placed in the same folder.
Once the test file has been created, we are good to write the tests.
This is a very nice blog that helps in setting up and writing tests in RTL/Jest.
A typical RTL test has 5 steps (reference)
- Imports
- Mock
- Arrange
- Act
- Assert
Imports:
This section includes all the necessary imports that are needed within the test file. Typically this includes:
- Importing React
- Importing the mocks from erm-components
- Importing the TestForm (if your component is rendered within a Form), renderWithIntl (function that renders the component to the dom with necessary translations)
- userEvent thats used to trigger events such as clicks, hover etc
Mock:
Apart from the stripes/intl mocks that were imported in the previous step from erm-components, this section includes mocks such as mocking an API request, or mocking a callback (spy). We use jests mocking API for this purpose.
Mocking an onSubmit or an onAdd callback:
or you can go one step further and mock the implementation:
This mock overrides the default onSubmit functionality and logs 'submit' to the console when its triggered.
Going through the mocks in erm-components should give you a better understanding of how mocking works.
Note: You can always clear the mocks using the mockClear() helper.
Arrange:
The next step is to render the component using either the render helper or renderWithIntl (which is just a wrapper around the render helper but also includes translations).
You can render the component simply by calling the render method.
Likewise we can use the renderWithIntl helper instead to include translations.
Act:
This step is where we can trigger user actions such as clicks, hover etc.
Checkout the userEvent api here which we use to trigger these actions.
To trigger a button with label Submit on it, you could do that using the userEvent.click api.
Assert:
Final step is to assert if your test is behaving as expected.
The following assertion expects the handleSubmit callback to be called once when the submit button is clicked.
Example:
Putting the above steps together:
import React from 'react';
import '@folio/stripes-erm-components/test/jest/__mock__';
import { TestForm, renderWithIntl } from '@folio/stripes-erm-components/test/jest/helpers';
import userEvent from '@testing-library/user-event';
import BasketSelector from './BasketSelector';
const onAdd = jest.fn();
const onSubmit = jest.fn();
const basketSelectorProps = {
'addButtonLabel':'add button',
'autoFocus':true,
'basket':[{
'id':'5650325f-52ef-4ac3-a77a-893911ccb666',
'class':'org.olf.kb.Pkg',
'name':'Edward Elgar:Edward Elgar E-Book Archive in Business & Management, Economics and Finance:Nationallizenz',
'suppressFromDiscovery':false,
'label': 'basketSelector'
}]
};
describe('BasketSelector', () => {
test('renders add To Basket button', () => {
const { getByText } = renderWithIntl(
<TestForm onSubmit={onSubmit}>
<BasketSelector {...basketSelectorProps}/>
</TestForm>
);
expect(getByLabelText(/basketSelector/i)).toBeInTheDocument();
userEvent.click(getByText('add button'));
expect(onAdd.mock.calls.length).toBe(1);
});
});
Running the tests locally:
To run the test in your local workspace:
1) Navigate to the app folder (eg: ui-agreements) and run
yarn test:jest
This should run all the tests within the app.
2) If you would like to run one specific test, you can specify the name of the test you would like to run like:
yarn test:jest BasketSelector;
Running Coverage report:
Once you run the tests locally you should have an artifacts folder generated locally in your app folder.
To generate coverage report:
1) From your app folder navigate to:
cd artifacts/coverage-jest/lcov-report/
2) In this folder you should see an index.html file which you can statically serve by installing a simple npm module like serve and run:
serve ./
This command will serve up a UI(defaults to localhost:5000) where you can see the coverage metrics
You can go into the specific file to see what lines/functions are missing the code coverage and can add them to the tests accordingly.
Idealy we would like to have 100% coverage on all the components. Sometimes it feels too trivial to cover certain cases, i.e. the effort in writing the test outweighs the condition you are checking for and that decision is left to the developer.
Debugging:
https://testing-library.com/docs/dom-testing-library/api-debugging/