Web Testing that Scales: Testing Decision Tree

AnthanhAnthanhSeptember 17, 2024
Web Testing that Scales: Testing Decision Tree

Choosing the Right Testing Strategy

When deciding on a testing approach, we have multiple options: unit, integration, functional, end-to-end (E2E), etc.

We could perform all tests as functional tests, but in the long run, this could be inefficient in terms of time and resources. Unit tests can be too technical or too low-level to validate functional specifications, and integration tests can be too complex to implement.

This time, we will see an example of a decision tree that can help us choose the best type of test depending on the test objective.

Article Series

This is part of a series of articles:

  • Part 1: Testing Strategies
  • Part 2: Testing Decision Tree
  • Part 3: Incremental Testing: A Monorepo Use-Case

We started from the previous article where we explored different strategies to test our web application. If you haven't read it, I recommend taking a look at it first as a recap.

Choosing Your Weapon (Testing Strategy)

What do we want to test?

Internal Code Testing

If we need to test an isolated piece of code or a method that is sufficiently complex or critical to the system, we should opt for unit tests. Examples include:

  • Mathematical calculations
  • State machines
  • Any function critical to the system

Isolated Component Testing

If we want to test an isolated component, we must analyze the type of component being tested:

  • Dumb/Presentational Components: Use a simple snapshot test to check that it renders as expected. Optionally, complement it with Storybook for interactive documentation. Examples:

    • Navigation bars with dynamic sections based on user roles
    • Information cards
    • Custom graphics
  • Components with Isolated Interactions/Behaviors: Use a functional test of an isolated component, implementing a test suite with Jest + waitFor. These tests programmatically simulate interactions, mock external dependencies, and verify expected component behavior. Examples:

    • A date range selector with restrictions
    • An IBAN input with synchronous and asynchronous validations
    • Checkout process behaviors in specific steps

This type of test brings significant value and, when well-developed, is efficient in resources and execution.

Behavior Testing

If we focus on testing at a functional level, we should determine whether we are testing an isolated behavior or if the test involves multiple pages or system elements.

  • If the test can be validated in isolation within a component (even with mocked dependencies), we can opt for a Jest + waitFor test.

  • If the test involves multiple pages or external dependencies, we must implement it as a functional test, typically using Cypress.

  • End-to-End (E2E) Testing: If the test represents a complete user flow, it should be implemented as an E2E test. This is crucial for validating critical system paths, such as:

    • Registration + Authentication
    • Purchasing processes
    • Core system functionalities
  • Cypress + Dispatch Optimization: If the test is an isolated behavior but still involves multiple steps (such as requiring authentication or pre-created resources), we can optimize it using Cypress + dispatch.

E2E: With or Without Mocks?

By definition, E2E tests should cover the entire system. However, real-world scenarios often involve external dependencies beyond our control. These dependencies can affect test stability and determinism, so we may need to mock them, regardless of whether the test is E2E or not.

Acceptance Criteria

All behavior-related tests can have an associated acceptance criterion described in Gherkin. In other words, a Gherkin specification can be covered by a test suite based on different testing strategies mentioned above.

Summary: Testing Decision Tree

At a glance, all of the above can be summarized in the following decision tree:

(Insert Testing Decision Tree Diagram Here)

More to Come!

This is just an example of how to make decisions when implementing a test, depending on the objective.

While there are many other types of tests, we can expand our decision tree later. In this case, we focused mainly on those mentioned in the previous publication.

In the next article, we will share a use case of a scalable test suite using a monorepo as a context.

I hope you found this helpful! Feel free to share your thoughts or experiences in the comments.