Testing modern web apps, Part 1: The big picture
This article is a part of “Testing modern web apps” series:
- Part 0: Automated tests are awesome
- Part 1: The big picture
- Part 2: End-to-End testing
- Part 3: API testing
- Part 4: Unit and integration testing
- Part 5: Visual regression testing
Before diving into specific tools and techniques we need to figure out what can be tested in a modern web application and for what reasons developers use one type of tests or another in their projects. In this article, I’ll walk you through the most common phases of building a web application and match them to corresponding types of automated tests. I’ll try to concentrate on the big picture so you can understand the fundamentals regardless of specific tools, frameworks and programming languages. Let’s start our journey now!
The journey of the button in the decoupled world
Imagine that you’ve got a client who wants to have a new CTA button on their website so that their users can quickly do some stuff online. That sounds like a good example of a feature request.
I’m not going to say that all you need to do is to write a few lines of CSS and JS because it’s simply not true.
Modern web apps are complex. They can contain hundreds of web pages with hundreds of components, integrations and embeds on each page.
In the real world, we break down (decompose, decouple) functionality into smaller independent parts to make web development simple again for individual developers.
For instance, building our new CTA button will most likely include the following phases:
1. Backend implementation
We may need to write or read data from a database, let site managers edit button label, make a request to a payment gateway or implement any other business-specific logic on the server side.
2. API endpoint
Backend logic needs to be securely exposed to clients. In a trivial case, you’ll have just one client: your own frontend written in React or any other frontend framework. Though it’s often a requirement to support multiple clients: web, mobile, partners, etc.
3. Visual component
This includes look & feel of various button states according to designs and UX specs.
4. Frontend implementation
Frameworks like React have been started as pure view-layer libraries and now they all suggest a rich ecosystem of tools for building competitive web solutions. Developers love these new tools so much that they tend to write more and more logic on the frontend.
5. Connect 1–4 to each other
Decoupling introduces an overhead of connecting different pieces of the app together. The better developers understand the entire architecture of the app, the easier it is to plug in their modules and components to the system.
Now let’s map the most popular types of automated tests to the diagram above.
Purpose: testing individual (isolated) functions, classes, components on programming language level.
Tools: Codeception, PHPUnit, Jest, Mocha, etc.
You should be able to find unit tests in almost every popular open source library or framework on GitHub. Unit tests reassure developers that pure functions and components work properly in various scenarios and correctly handle edge cases.
If you maintain a library (internal or external) and other components/developers rely on it, then you need to cover it with unit tests. We’ll discuss unit testing in the upcoming posts.
The truth is that you most likely don’t need unit tests in any other cases. When was the last time you wrote a custom sorting function as part of a client project? It is an edge case, and so are unit tests. You can simply skip unit testing for now and come back to it when your app is covered by other types of tests.
Purpose: testing processes or components to behave as expected, including the side effects.
Tools: Codeception, Jest, Mocha, etc.
Both integration and unit tests focus on the code: functions and components written in a certain programming language. There is one important difference though.
In unit tests, we mock all dependencies and external functions to test isolated pieces of code. In integration tests, conversely, we keep most of the dependencies in place and test how well individual units play with each other. It makes integration tests much more useful in real-world web development. We will look deeper into integrations tests in the upcoming posts.
Purpose: testing the entire system on browser level; behaving like a real user.
Tools: Codeception, TestCafe, Cypress, etc.
While unit and integration tests focus on testing code, end-to-end tests (sometimes called E2E, Acceptance or Functional tests) focus on testing site features.
Instead of executing specific functions, end-to-end tests interact with UI elements rendered in a browser: clicking, typing, submitting, etc.
These type of tests used to be expensive and hard to set up, but things are constantly improving and today almost every website can afford end-to-end testing to ensure that business-critical features work as expected. We will dive into end-to-end testing tools in my next article.
Purpose: testing API endpoints on HTTP client level.
Tools: Codeception, Postman, SuperTest, Frisby.js, etc.
If your API endpoints work as expected, then most likely your backends and backend-frontend connections work as expected too. Similarly to end-to-end testing, API testing doesn’t require any mocking-up which produces more realistic results. At the same time, API tests are much faster than end-to-end tests, because they don’t require rendering pages in a real browser.
These type of tests are my favorite because of their performance-value ratio.
Visual regression tests
Purpose: taking screenshots and comparing to base images in order to verify visual appearance of web pages.
Tools: Gemini, BackstopJS, Percy, etc.
There are services which conflate end-to-end and visual regression testing. I find this approach confusing because the purposes of these two types of tests are different. E2E tests focus on testing end-user features on a real website while visual regression tests verify look & feel of individual components in different browsers and viewports. For example, E2E test can verify that the checkout flow works fine on the site. Visual regression tests can verify that form inputs and buttons are rendered as per designs on mobile, tablet and desktop. Maintaining visual regression tests may be a little bit more complex compared to other tests so I’d recommend coming back to them when your app is well-covered by E2E, API, and integration tests.
It is important to look at the big picture of your app and think about what and why you want to test. There are many types and subtypes of automated tests at your service. Most popular are Unit, Integration, End-to-End, API, and Visual regression tests. In the upcoming posts, I’ll take a deeper look at each of them, compare available tools and share my own recommendations for a quick start. Stay tuned!