Test Pyramid in JavaScript TDD

Image By Neha Gupta

Introduction

Test-driven development (TDD) is a powerful approach that involves tests before writing the actual code. It helps ensure your code is reliable, maintainable and meets the specified requirements. When incorporating unit tests in JavaScript using TDD, you should focus on the essential logic of a new feature, cover edge cases, and follow the structure of the Test Pyramid.

Steps for TDD:

  1. Write a Test First: Start by writing a unit test for the new feature or function, even though it doesn’t exist yet.
  2. Run the Test: Since the functionality has not yet been developed, this test should initially fail.
  3. Implement the Feature: Write just enough code to pass the test.
  4. Refactor: Refactor the code to enhance structure after the test is successful, making sure that all tests still pass.
  5. Repeat: Continue using the same procedure while adding tests for new features, edge cases, and situations.

Test Pyramid

The Test Pyramid is a visual guide to help you understand the different types of tests you should have in your application. It suggests that you should have:

  1. Unit Tests (Many): Focus on testing individual components in isolation.
  2. Integration Tests (Some): Test how different parts of the system work together.
  3. End-to-End Tests (Few): Test the entire application from start to finish.

Scenario To Understand

Let’s assume you’re working on a new feature that calculates the total price of items in a shopping cart, including a discount if the cart total exceeds a certain threshold. Here’s how you can use TDD to write unit tests for this feature:

Unit Tests (Test Pyramid Base)

You can use a testing framework like jest and mocha to implement unit testing in your application. Small, distinct functional chunks — typically single functions, methods, or components — are the subject of unit testing. They provide accurate feedback on failures, are quick, and operate frequently.

What to Cover:

  • Pure functions and business logic: Analyse calculations, data conversions, and other logic separately.
  • Components (React, Vue, etc.): Use tools like Jest and the React Testing Library to test how small, isolated UI components render in a framework like React.
  • Utility functions and helpers: Test utility features such as array operations, integer computations, text manipulation, and side-effect-free pure logic.
  • State management: Use mock dependencies (such as Redux reducers and hooks) to test how your components respond to state transitions.

Integration Tests (Test Pyramid Middle)

Integration tests make assurance that various modules or components function as intended. Typically, they test sets of units communicating with one another, like services with other services or a database with the API.

What to Cover:

  • API routes and controllers: Examine the interactions between various services, such as those between a database and an API controller.
  • Database interactions: When a service communicates with the database, make sure that the data is retrieved, stored, and transformed correctly.
  • Third-party services: Make sure your application manages data from external services correctly by mocking or testing external APIs.
  • Middleware: Examine the data flow via various layers, such as the Express middleware’s request/response flow.

End-to-End Tests (Test Pyramid Top)

End-to-end tests verify that the application operates as intended from the user’s point of view by simulating actual user interactions with the complete system. These tests are more delicate and slower, but they are essential to making sure the program functions as a complete.

What to Cover:

  • User workflows: From the beginning to the end, test user scenarios like registering, logging in, adding products to the cart, and checking out.
  • Critical user paths: Verify that the most crucial and common user actions — such as logging in, checking out, or filling out a form — are functioning.
  • UI behaviour: Verify the way UI components — such as buttons, forms, and models — respond to user input.
  • Integration of different services: Verify the interoperability of the application’s various components, including the database, back end, and front end.

Practical Factors to Consider When Splitting Functionality Among Test Types

  1. Test Complexity:
  • Unit tests are quick to run and easy to set up, but they require more mocking and isolation.
  • Integration tests need real services or realistic mocks, so set up environments like test databases or mocked APIs.
  • E2E tests are complex, so ensure the test environment (e.g., browser, server) mirrors production as closely as possible.

2. Execution Time:

  • Because unit tests are quick, they ought to be given priority. Integration testing should balance speed and accuracy. Limit E2E tests, especially in CI/CD workflows, to avoid long test suites.

3. Feedback Loop:

  • Make sure that each pull request or commit includes unit tests. Less frequently, integration tests could be performed at regular intervals or perhaps on each push. To prevent slowing down the development cycle, E2E tests can be triggered nightly or on large releases, but they should run in CI/CD pipelines.

4. Flakiness:

  • Unit tests are often stable since they are separated. Sometimes real-world problems (like database status) cause integration tests to fail. Environmental variables or asynchronous behaviour might cause E2E tests to become unstable; to fix this, employ tools like retry logic or waitFor commands.

Conclusion

You can achieve a balance between quick feedback (through unit tests) and in-depth validation (through E2E tests) by carefully dividing functionality across unit, integration, and E2E tests. This will also guarantee that your test suite is dependable, maintainable, and compliant with the Test Pyramid principles.

Queries and Doubts

  • If you have any confusion or doubts, please post in the comment section.
  • Follow me on LinkedIn for more such blogs. You can also share some hot ideas for upcoming blogs on my LinkedIn.
  • If you want a 1:1 session with me feel free to book here on my topmate.
Thanks for reading ❤️