iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🧪

Defining Frontend Testing Strategies Based on Product Context

に公開

Overview

I believe that frontend testing policies should be determined according to the product's situation, so I'd like to write about this casually. Ideally, I want to align with the "Testing Trophy" proposed by Kent C. Dodds (defining the layers of Static, Unit, Integration, and E2E, and emphasizing integration tests). However, since there are product phases and team circumstances in practice, I think it is most realistic to adjust the distribution while using the Testing Trophy as a foundation.

For example, if a product has already been released but has almost no automated tests, it is easier to start by preparing 1–3 minimal E2E tests to verify only the primary user journeys. Under that safety net, you can then proceed with refactoring while gradually increasing integration and unit tests.

Definition of Terms (Aligned with the Testing Trophy)

First, I will briefly align the terms used in this article with the classifications of the Testing Trophy.

  • Static Analysis (Static)
    A layer that catches potential bugs without executing the code, such as type checks and linters. In the Testing Trophy, it is positioned as the foundation.
  • Unit Testing (Unit)
    Tests that verify the behavior of individual functions or modules. They isolate external dependencies and check whether the output is as expected for a given input.
  • Integration Testing (Integration)
    Tests that verify behavior when multiple components are connected. They focus on whether results are updated by user operations, such as UI components and state management, or API mocks and display updates. The Testing Trophy recommends making this layer the thickest.
  • E2E Testing (End-to-End)
    Using a browser or similar tool, these tests verify a series of user operations (user flows) in a form close to production. The concept of the Testing Trophy is to keep their number small and cover only the primary user journeys.
  • Visual Regression Testing (VRT)
    Tests that detect visual changes through image differences. They confirm whether the styling is broken even if the DOM is the same. While this is also outside the scope of the Testing Trophy, it is effective as a supplement when design quality is important.

Why should it be "determined according to the product's situation"?

First, because the optimal test distribution changes depending on the product's situation. The combination of factors—the phase (launch / growth / difficult maintenance state), domain risk (payment, medical, public, etc.), frequency of changes, screen complexity, team size and experience, and the amount of technical debt—changes where failures are most problematic and where can be protected with minimal effort. Therefore, instead of a one-size-fits-all approach for all teams, I believe it is realistic to layer tests starting from where they are most effective according to the situation.

Next, because the structure and dependencies of existing code influence the order of priority. Increasing unit tests from the start for tightly coupled implementations tends to lead to brittle tests tied to implementation details. On the other hand, covering only the primary user journeys with E2E tests first allows you to secure broad peace of mind that "nothing is majorly broken," and from there, you can safely refactor and increase granularity in the order of integration tests followed by unit tests.

Furthermore, to achieve maximum effect with limited time and people, it is efficient to strongly protect behaviors that are close to the user. The Testing Trophy is a helpful way to organize this. Reducing noise with static analysis as the foundation, placing integration tests in the leading role, supporting logic with unit tests, and keeping E2E tests short and focused on key points—I feel this balance works well with the fast-changing frontend. However, since this is an ideal, we adjust the distribution based on the Testing Trophy according to the product's circumstances.

Concrete Examples of Policies Based on the Situation

When the product is already released and there are no tests

Prepare 1–3 minimal E2E tests that check only the primary user journeys, such as Login → Purchase/Signup. This allows you to continuously verify that the user flow is not broken. The reason for not starting with unit tests is that they are less likely to lead to broad peace of mind, and tests tied to implementation details are easily broken during refactoring. Once the safety net is in place, you can thicken the integration tests and solidify calculations and formatting with unit tests. In this way, you move closer to the order of the Testing Trophy.

Concrete example (EC): First, run only "Product Details → Cart → Purchase Completion" through E2E tests. After that, cover the sequence of Search Form → API Mock → Result Display → Paging with integration tests, and verify inventory allocation and shipping calculations down to the boundary values with unit tests.

When tests exist but are unstable or slow

The goal is to return to a state where you can simply trust the test results. Since processing that depends on wait times or dependencies on external APIs tends to be the cause of instability, replace those parts with mocks/stubs. Move E2E tests that can be replaced to integration tests to speed up cause identification.

Concrete example (B2B SaaS Dashboard): E2E tests for report generation were failing due to limitations of an external analytics service. Therefore, we replaced the report API with a mock with a fixed response and checked the connection with the visualization component using integration tests. This stabilizes reproducibility and significantly shortens execution time.

New Development (Building from Scratch)

It is important to decide the division of testing responsibilities at the same time as the design. For example, set up Static Analysis → Unit Testing → Integration Testing first, and create a configuration where only important user flows are verified with a small number of E2E tests. Design the components and state management so they are easy to test later (e.g., making interactions with external systems replaceable, and separating calculations or logic into small functions). By doing this, it becomes easier to handle feature additions and specification changes while maintaining the Testing Trophy approach (emphasizing integration tests).

Concrete example (Internal Tool): Maintain only one E2E test for the approval flow: "Application → Approval → History Reflection." Use integration tests for list filtering and CSV output, and cover amount or string formatting with unit tests.

High-Risk Areas (Payment / Billing / Authorization, etc.)

In areas where a deviation from specifications could lead to serious incidents, protect them with multiple layers. Use unit tests for amount calculations and credit logic down to the boundary values, use integration tests for coordination between the UI and APIs, and use short E2E tests for the user flow until completion. The idea is to maintain the balance of the Testing Trophy while intensifying E2E tests only at key points.

Concrete example (Payment): Exhaustively cover amount calculations when using both discounts and points with unit tests. Confirm the sequence from the "payment screen → external gateway response → result display" with integration tests, and maintain only one E2E test for the flow until completion.

Design Systems and Common Components

This falls outside the standard Testing Trophy, but if you want to detect visual breaking changes early, it is effective to supplementally use Visual Regression Testing (VRT) in combination with Storybook or similar tools. The basic approach remains using integration tests for interactions and unit tests for underlying utilities.

Concrete example (Design System): Target only major variants of buttons, modals, and forms for VRT, and verify "form submission → validation → error display" with integration tests.

Points to Note

  • Do not over-increase E2E tests: Limit the scope to important user flows such as "Login → Purchase Completion." Aim for a total execution time of within 5 minutes, and verify branches like the presence of coupons or differences in payment methods through integration or unit tests.
  • Do not depend on implementation details: Verify the results on the screen rather than DOM class names or internal states. Example: "Product name is displayed," "Clicking the button opens a confirmation dialog," "Total amount is 3,300 yen."
  • Avoid abusing snapshots: Avoid comparing the output of entire pages; limit it to specific outputs you want to protect (e.g., heading text, error messages). If necessary, replace them with intentional checks like "Element is displayed" or "Link destination is correct."
  • Clarify responsibility for failed tests: Establish rules such as "the person on duty performs the initial response," "tests failed due to your own changes must be fixed within the same day," or "if a fix is not possible, temporarily disable the test and create an issue."

Summary

The ideal is distribution aligned with the Testing Trophy (static analysis as the foundation, primarily emphasizing integration tests, supporting logic with unit tests, and narrowing down E2E to a select few). However, since there are constraints in practice, we adjust the density according to the product's situation. I believe this approach is the realistic solution for achieving both speed and confidence.
If you have any tips for distribution or operational improvements at your workplace, or if you have a different perspective, please let me know through comments or feedback!

References

https://kentcdodds.com/blog/the-testing-trophy-and-testing-classifications

https://zenn.dev/coconala/articles/f048377f314507

GitHubで編集を提案

Discussion