Conquer Every User Journey: Building Robust E2E Tests with Playwright- part 1
Tags: testing, webdev, javascript, beginners
Published: 2024-03-20 09:33:15

In everyday software development, making sure parts of our application work as expected will involve us testing the app. This could include clicking a button, a dropdown, or an input field to make sure they behave as expected. Testing very simple applications manually can get the work done and make you sleep at night. However, manual testing becomes tedious and inefficient for very large applications.
 
Say Hello to **Automated testing**.In part 1 of this post, you will learn about what automated testing is, the types of automated tests, the tools used for automated testing, and of course the fun part... we get our hands dirty with some code examples in part 2.

## What is Automation testing
Let's assume we are new to the English language, and break down these terms :

- **Automation**:This refers to using machines or software to do tasks that would normally be done by a person. Imagine a car wash that automatically cleans your car instead of someone scrubbing it by hand.

- **Testing**:In the world of software development, testing involves checking if a program works as expected. We want to make sure there are no bugs or errors that prevent it from functioning properly. Think of it like testing a new recipe in the kitchen to see if it turns out delicious!

From the above definitions, we can say automated testing is writing code that tests our applications for us. So we use testing tools to say "Hey you see this element, when I do this action, I expect these results". and out test is said to "pass" when the **expected result** = the **actual results** from interacting with those elements when the test is running.

## Types of Automated test
There are primarily 3 types of testing: Unit test, Integration test, and End-to-end test (E2E). These 3 test are usually presented by the following pyramid

![Testing pyramid](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tfzg62yat15f1byd087t.png)

This pyramid indicates complexity in writing and maintaining tests as you go up the pyramid. This indicates we should write more Unit tests which are easier to write and run faster. On the other hand, we should write a few End-to-End tests for crucial user journeys since they are more complex to write and maintain and also take more time to run.
Let's take a closer look at each of these types of tests: 

- **Unit test**: Unit test involves testing individual, isolated pieces of code e.g. testing a component, utility functions, etc. In frontend applications, it usually makes sense to write unit tests for crucial business logic and heavily used utility functions. with this type of test, **mocking** is used to abstract out 3rd party logic and API calls

- **Integration test**: Integration testing is all about checking how 2 or more of these interact and collaborate. It verifies that when you perform actions on one component (like clicking a button), the other components react as expected (like displaying a new page or updating information). While **unit testing** focuses on the individual functionality of each component in isolation, integration testing brings them together to ensure they play nicely as a team.

- **E2E test**: Imagine a user starting on the homepage of your application, clicking through different pages, interacting with various components, and finally reaching a specific goal, like completing a purchase. **E2E** testing mimics this entire user experience from beginning to end. **E2E** testing focuses on the user's perspective. It checks if the application flows smoothly, functions as expected at each step, and ultimately delivers a satisfactory experience for the user.

## Popular Tools used for Frontend Automated testing

- **Unit test/Integration test**: React Testing Library, Jest, Vitest
- **E2E test**: Playwright, Cypress

Before we go ahead to start writing some test, let's look at **test doubles**

## A Hand-shake With Test Doubles
Test doubles play a crucial part in automation testing as they allow us to abstract away logic like API calls, and calls to 3rd part libraries and fully focus on testing a component or a group of components in isolation.

Let's take a closer look at the 3 most common test doubles and their differences:

1. **Stubs:**
   - **Function:** Stubs are the simplest type of test double. They are pre-programmed to return specific, predetermined values when their methods are called.
   - **Focus:** Stubs don't care about how they are called. They simply provide a fixed response for specific inputs, allowing you to test the component's behavior under those conditions.
   - **Use case:** Stubs are ideal for testing how a component reacts to specific data or interactions, without the complexity of dealing with real dependencies.

    **Example:** You are testing a component that displays a welcome message based on the user's name. You can use a stub to simulate an external API that fetches user data and always return a specific name, allowing you to test the component's logic for displaying that name.

2. **Spies:**
   - **Function:** Spies are similar to stubs but offer additional capabilities. They can record information about how they are called, such as the number of times a method was called or the arguments passed to it.
   - **Focus:** Spies care about how they are called and can be used to verify specific interactions occurred during the test.
   - **Use case:** Spies are helpful when you need to ensure that certain methods of a dependency were called with the expected arguments. They allow you to assert not only the outcome of the component but also the interactions it had with its dependencies.

   **Example:** You are testing a component that sends data to an analytics service. You can use a spy to track how many times the analytics service method was called and with what data, ensuring the component interacts with the service as expected.

3. **Mocks:**
   - **Function:** Mocks are the most versatile type of test double. They combine the functionalities of stubs and spies, allowing you to pre-program expected behavior and verify interactions. Additionally, Mocks can simulate more complex behaviors, such as throwing errors or returning different values under specific conditions.
   - **Focus:** Mocks offer the most control over the behavior of the test double. You can define expectations for how they should be called and verify if those expectations are met during the test.
   - **Use case:** Mocks are useful for complex scenarios where you need to control the behavior of a dependency in different ways and ensure the component reacts appropriately.

   **Example:** You are testing a component that interacts with a database. You can use a mock to simulate different database responses (success, failure) and verify the component handles them correctly.

### Choosing the Right Double:

The choice between stubs, spies, and mocks depends on your specific testing needs and the complexity of the dependency being replaced.

- Use **stubs** for simple scenarios where you only need to control the output.
- Use **spies** to verify how the component interacts with the dependency.
- Use **mocks** when you need complex behavior control and verification of interactions.