In the previous article, I explained the difference between types of tests for front end applications and answered the question of which tests are required for different types of projects. In this article, I will recommend the best modern frameworks and tools to handle front end application testing for React. I will also highlight common testing mistakes that one should avoid.
All three frameworks will do the job, and with the help of other libraries will make testing React applications efficient. My recommendation is Jest because of its simplicity, no need to add separate assertion libraries or create expensive configs. Tests are executed very fast, which is important for large projects with high coverage.
Jest provides very useful options for mocking. You can mock whole modules for your application using manual mocks, you can mock any es6 module for specific tests only using class mocking or simply just mock particular functions. From my experience, mocking is something used very often to make our tests closed in a bubble, separated from external dependencies. That mechanism needs to be easy to use and cover all possible mocking scenarios, and that’s what Jest provides. The list of predefined assertion/matchers is also huge, with no need to extend it.
My recommendation: Jest
Component unit tests/Integration tests
Jest is a perfect choice for standard unit tests of pure functions. However, when it comes to the component or integration testing, Jest starts to not be enough. By default, Jest and React come with a react-test-renderer package which can transform components into JSON objects. This mechanism is used for snapshot testing.Jest is a perfect choice for standard unit tests of pure functions. However, when it comes to the component or integration testing, Jest starts to not be enough. By default, Jest and React come with a react-test-renderer package which can transform components into JSON objects. This mechanism is used for snapshot testing.
Snapshot testing is used for making a DOM nodes JSON representation of a component and storing it next to the component file. We can compare a previously taken snapshot with a new one the next time tests are run. If there is a difference, tests will fail. Snapshot testing in most of the cases is a waste of time. It can only catch changes to the DOM structure, so if you make changes to components, you’ll need to update our snapshots. It can be only useful when you are 100% sure that component is working and want to prevent someone from changing it. Any changes will cause your test check to fail.
A much better idea is to simulate DOM rendering outside the browser. You are able to render components and see a rendered DOM structure in the console. You can also simulate user events on specific DOM elements and observe how your components are changing. There are 2 popular solutions you can use: Enzyme or Testing Library.
Enzyme library was designed only for React, it comes with 2 ways of testing: shallow rendering – which is helpful to test components as a unit without child components. If your components have very high granulation with nested child and grandchild components, it will mean that each of the children will need to have its own specific test to cover whole logic. It is not a very efficient approach. Full DOM rendering in Enzyme is rendering virtually the whole component tree. You can interact with components simulating user events. You can use assertions anytime to check if parts of a component are rendered correctly after each interaction.
Testing library was designed for all major SPA frameworks. There are versions for Angular, Vue, Svelte, React and other less popular ones. Testing library is doing full DOM rendering with a large set of helper functions to query and find specific nodes.
Full DOM rendering without a browser is possible with both Enzyme and React Testing Library but writing tests in React Testing Library is a much better experience than writing tests in Enzyme. The querying of data is easy. Firing events are very intuitive. Testing Library focuses more on having as close user experience to E2E testing experience as possible.
With React testing Library you are writing tests imitating user interactions in real browsers. Usually, each test will be constructed in similar way:
- Rendering a component, group of components or page, each Single Page Application framework has its own rendering methods (for React).
- Querying needed elements to make interactions. It’s always good to make separate data-testId attributes for all elements used by tests. Searching over the project, you can easily find all elements which are used by your tests. This way you are not using class/ids which are used for styling and may change because of some styling update.
- Firing events on previously queried elements like button clicks, or filling inputs/textreas etc.
- Query elements you want to check and use js-dom helper library to create proper test assertions. Usually, you check if some element exists in DOM as we want to test from the user’s perspective. This way you can see if the changes are seen by the user as expected.
My recommendation: React Testing Library
There are 2 different types of E2E frameworks: Selenium and non-Selenium ones. Selenium tests use a webdriver to interact with browsers. Non-Selenium frameworks interact with a browser directly. Years ago Selenium frameworks were ruling the E2E testing world but recently we can observe that non-Selenium ones are getting more and more popular.
There are a lot of different comparisons between selenium and non-selenium solutions. The main disadvantage of using non-selenium solutions is missing the functionalities like supporting multiple browser tabs which cannot be achieved without a webdriver. E2E tests should always focus on application functionality and follow paths users follow. Tests which require switching between tabs and leaving one application context should be avoided, and if needed covered manually to reduce further tests possible instability.
The most popular frameworks which interacts directly with a browser are:
Both frameworks provide similar capabilities. The choice is difficult. In past projects both did their job without any problems but when starting a new project I’d go with Cypress because of its popularity among the community. Community support is a huge thing, it means that if any problem appears, you will always find a proper solution which someone has already faced before and solved.
Cypress works fully asynchronously. Before running it, you need to decide which browser the tests should run on (the browser needs to be installed on your computer or Docker container used by pipeline), on what resolution etc. Typical test consist of:
- Visiting specific page url you want to test
- Query and Interact with elements on page
- Query element on page you want to check and use one of provided assertion methods to check if you get what we expected
Our recommendation: Any non-selenium One (preferred Cypress because of the community support)
Front end testing frameworks: common problems
So you have decided what tests you need to write for your project based on the client requirements. Next, you need to pick up the recommended frameworks for doing the job. You are ready to start writing your tests…but you need to be very careful. Tests can make your life easier but when the project is growing and growing, you can end with a bunch of possible problems and big headaches.
- Flaky E2E tests – term is used when you have tests which sometimes pass and sometimes fail. Making your pipeline unpredictable. Test flakiness is caused by small mistakes you have made in the test codebase. Main reason may be the test dependencies, not waiting till asynchronous action will finish or inconsistent initial state of application. Applications empty or full of data may need a different testing approach for some features like long lists with paginations.
- Sleep function – using sleep function is a bad practice. Sometimes when you write tests, you may put sleep time to wait a specific amount of seconds and then check if the assertion is correct. You should never do that and use alternative methods of waiting if possible. Sleeps are making your tests run longer and slowing down whole pipeline execution.
- Huge E2E execution time – E2E tests are time expensive as each time test needs to open the browser and execute necessary initial user actions like login and accessing specific routes. Covering all possible user journeys with E2E tests will make your pipeline very heavy. Because E2E is expensive, you should cover only the most important paths with it. Some features may be covered just by units or integration tests which will run much faster.
- Time-consuming mocking – some components rely on multiple data sources from outside like redux store, i18n providers, graphQl wrappers or context API. To be able to achieve full DOM rendering and test components effectively, you will need to mock all the things to be able to render components correctly. Also, the child components may have their own data requirements which are not passed as props from the parent. Creating a test for such a situation may be difficult and time-consuming. Hence it is much easier to cover such a component with an E2E test which doesn’t care about component implementation details. Alternatively, you can consider some refactoring to be able to test the functionality with a set of small units.
- Snapshot testing – snapshot tests are not giving you any useful information except the fact that you know that component DOM has changed. Instead of doing snapshot tests, you should invest time doing other types of tests.
- Running always all tests – running all possible tests for all branches will make your CI tool always busy. Especially for larger projects, when execution of all tests may take around 3 hours. Better to execute different tests on different branches. It’s perfectly fine to execute only units on feature branches and check some most important E2E on the develop branch and run a full set of tests just on staging.
Testing solutions are evolving very fast. New frameworks come and go. Because of this, it is hard to be up to speed on what tools and frameworks should be used. I hope you are no longer confused about what should be part of your new project development setup. Picking up: Jest, React Testing Library and Cypress is a safe choice for your new project in 2022.