Testing UIs
This section describes how developers will test codebase related to the UI.
Testing stack
All of our UI tests MUST be done using Jest as the main testing framework with redux-saga-test-plan as the sagas testing tool and react testing library as ui components testing tool. The test's code MUST be written using Typescript and ran using ts-jest to have type checking support.
What to test
UI components
Sagas
Reducers
Selectors
Action creators
Utility functions
Testing sagas
Sagas lets us handle side-effects by letting us create generators that handle redux actions. One of the main advantage of sagas is its testability, as the effects are widely used in sagas are highly testable.
These handler MUST be tested using redux-saga-test-plan, and the test MUST exercise the whole execution, from whole module's handler (its main handler or sagas) through all the execution of the handlers that will react to the action we're going to dispatch. This approach makes sagas tests easy to build and maintain, and brings value to the tests as they're going through as much as the whole flow as possible.
Handlers' tests MUST be tested through their interaction with the effects, mainly with the put one, as their purpose is to handle actions and produce new ones. Most of the tests will surely contain a provide call, used to mock effects, one or multiple put calls, to verify that the handlers dispatches the actions as expected and a single dispatch followed by a run call to execute the whole sagas until completion.
The mocking of resources external to the module that is being tested SHOULD be done using the redux-saga-test-plan providers by taking advantage of the different effects provided by redux-sagas. Effects like call, apply or others SHOULD be used whenever it's possible when a function is called in a test to allow mocking them easily. Jest SHOULD NOT be used to mock modules or functions.
The text in the describe and its MUST follow these guidelines:
The main describe of a saga or main handler MUST start with when handling, indicating that what is going to be tested is a handler. Following the when handling phrase, a description of the action (or the intention that the action) to be handled MUST be written. E.g. when handling the action that signals a successful fetch.
Internal describes will differentiate execution contexts, they follow the same rules as described in the Describing and building context section.
The its SHOULD describe what is expected from the test, starting with a should put and the description of the actions that should be put when executing the handler.
Sagas example
Sagas test example
Testing reducers
Reducers are functions that take a state and an action and return a new state. This new state MAY contain changes programmed to occur for the given action.
Tests SHOULD vary the initial states and the different parameters of the action to be tested if required. The assertions MUST be done to whole returned state as an action could modify any part of the given initial state.
When reducing an action, we SHOULD use the action creator, making the tests easier to maintain.
To standardize the way we write describes and its:
Main describes SHOULD start with describing which actions are going to be reduced by using the phrase when reducing the action followed by a description of the action. We SHOULD NOT use the action type to describe the action to be tested, as the type might change.
The its SHOULD describe the way the returned state changed in a clear way. E.g: it should return a state with the error nulled and the fruits set.
Reducer example
Reducers test example
Testing selectors
Selectors are methods that takes a state and returns a section of that state or a transformed section of the state.
There are two types of selectors, memoized selectors and normal or common selectors. Normal or common selectors MUST be tested like any other unit tested function and memoized selectors SHOULD be tested using the resultFunc or the function that receives the memoized parts of the state and returns the wanted value.
Describes and its MUST be written as described in the Describing and building context section.
Selectors example
Selectors test example
Testing action creators
Action creators are in charge of creating the actions that will later be processed in the reducers. Although action creators are usually simple, they MUST be tested to ensure that they do what we expect.
Describes and its MUST be written as described in the Describing and building context section.
Action creators example
Action creator test example
Testing ui components
For testing UI components we are using React Testing Library. We are using this library instead of other options like enzyme as it lets us test components taking into consideration how the user will interact with it instead of the implementation itself. We won't be interacting with react component instances, instead we are interacting with dom elements in the way they are rendered.
What to test
The purpose of this ui components test is to test the behavior of the component and how the user interacts with it. In many cases there might be components that are connected to the redux store to get some properties and call some actions. We usually handle this by making a separate container file that will interact with the store. These files are out of the scope for this kind of testing. We are testing how the component behaves with different props values, we shouldn't care the source of those values (redux store, context, parent component), but how they impact the final render.
Rendering
Every test should start calling the render function. This will render not only the component we are sending as the parameter, but also all the children. We should avoid doing this in test setup (beforeEach) as it can cause issues making the dom tree have more components that we want. We should save the result in a property name screen. Doing this will let us get all the properties we need in the test without the need to change constantly the destructuring.
If the document will have multiple tests and the component has multiple props we can create a function at the top of the file that will take care of setting default props and render the componet.
Queries
Queries are the methods that Testing Library gives you to find elements on the page. The are different kinds of queries that we can use to access the elements. We should try to always use the queries that are Accessible to everyone as they are the ones that reflect the experience for all users (visual/mouse and using assistive technology).
Those queries are getByRole, getByLabelText, getByPlaceholderText, getByText, getByDisplayValue. If we can access the element using this queries it probably means that the component is not accessible.
Testing user events
We won't be able to access functions inside the component so we should imitate the user behavior that leads to that functions being called. To do this we are using userEvent library.
Debugging
To debug the test and what is the current state of the dom in an specific time we can use debug function which is one of the properties returned when calling render
Full example of ui component test
Directory structure
Tests MUST be placed alongside the files or modules that they will exercise, with the same name, but with the extension spec.ts instead of ts.
A file structure MUST look like this:
Last updated