Mastering Frontend Testing: A Comprehensive Guide to Unit, Integration, End-to-End, and Acceptance Testing
frontend testing unit testing integration testing acceptance testing end-to-end testing web development bugs performance testing frameworks Cypress Selenium Jest Mocha

José Matos

09 May 2023

Mastering Frontend Testing: A Comprehensive Guide to Unit, Integration, End-to-End, and Acceptance Testing

    Frontend testing is a crucial part of any modern web development project. As a frontend developer, you must ensure that your code is tested thoroughly to avoid bugs and performance issues.

    Types of frontend testing

    There are many types of frontend testing available, each with its own benefits and drawbacks. In this article, we will explore four of the most common types of frontend testing:

    • Unit testing
    • Integration testing
    • End-to-end testing
    • Acceptance testing

    Unit testing

    Unit testing is the process of testing individual units or pieces of code in isolation to ensure that they function correctly. A unit is typically a function, class, or module.

    Unit tests are written using a testing framework, such as Jest or Mocha. The goal of unit testing is to identify bugs and errors as early in the development process as possible. This can save time and money in the long run by reducing the amount of time spent searching for and fixing bugs during later stages of development.

    Unit tests should be simple and easy to write, with a clear focus on testing the functionality of a specific unit of code. They should not have any dependencies on other parts of your application, such as the database or network, as this can make them harder to write and maintain.

    For example, let's say we have a function that calculates the distance between two airports:

    
    function calculateDistance(from, to) {
      const {lat: lat1, lon: lon1} = from;
      const {lat: lat2, lon: lon2} = to;
    
      const R = 6371e3; // earth's radius in meters
      const φ1 = lat1 * Math.PI / 180;
      const φ2 = lat2 * Math.PI / 180;
      const Δφ = (lat2 - lat1) * Math.PI / 180;
      const Δλ = (lon2 - lon1) * Math.PI / 180;
    
      const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
              Math.cos(φ1) * Math.cos(φ2) *
              Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
      const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    
      return (R * c) / 1000; // distance in kilometers
    }
    

    We can write a unit test to ensure that this function works correctly:

    
    test('calculateDistance returns the correct distance in kilometers', () => {
      const from = {lat: 51.5074, lon: -0.1278};
      const to = {lat: 40.7128, lon: -74.0060};
      const distance = calculateDistance(from, to);
      expect(distance).toEqual(5566.051079517491);
    });
    

    This test has no dependencies on other parts of our application and tests the functionality of our `calculateDistance` function in isolation.

    Integration testing

    Integration testing is the process of testing multiple units of code together to ensure that they function correctly as a group.

    Integration testing is particularly important for frontend development, where multiple components must work together seamlessly to create a functional user interface.

    Integration tests should be written in a way that reflects the actual behavior of your application. This means that they should take into account all of the dependencies of your code, such as the database or network.

    For example, let's say we have a component that displays a list of flights:

    
    function FlightList({ flights }) {
      return (
        <ul>
          {flights.map(flight => (
            <li key={flight.id}>{flight.origin} -> {flight.destination}</li>
          ))}
        </ul>
      );
    }
    

    We can write an integration test to ensure that this component works correctly with actual flight data:

    
    import { render } from '@testing-library/react';
    import axios from 'axios';
    import MockAdapter from 'axios-mock-adapter';
    import { FlightList } from './FlightList';
    
    const mock = new MockAdapter(axios);
    
    test('renders a list of flights', async () => {
      const data = [{
        id: 1,
        origin: 'LHR',
        destination: 'JFK',
        departure_time: '2022-01-01T12:00:00.000Z',
        arrival_time: '2022-01-01T17:00:00.000Z',
      }, {
        id: 2,
        origin: 'JFK',
        destination: 'LHR',
        departure_time: '2022-01-02T12:00:00.000Z',
        arrival_time: '2022-01-02T17:00:00.000Z',
      }];
    
      mock.onGet('/flights').reply(200, data);
    
      const { findByText } = render(<FlightList />);
    
      await findByText('LHR -> JFK');
      await findByText('JFK -> LHR');
    });
    

    This test ensures that our `FlightList` component works correctly with actual data from our API using the `axios-mock-adapter` library. If there is an error or bug, this integration test should help pinpoint the issue.

    End-to-end testing

    End-to-end testing is the process of testing your application's entire flow or user journey from start to finish.

    This type of testing is often performed by automated tools, such as Cypress or Selenium, to simulate real-world user behavior and interactions with your application.

    End-to-end tests are useful for identifying any issues or bugs that may only appear when multiple components are working together. They can also help ensure that your application meets your business requirements and user expectations.

    For example, let's say we have a user journey where a user searches for flights and books a ticket:

    
    describe('Flight booking journey', () => {
      it('allows a user to search and book a flight', () => {
        cy.visit('/');
    
        cy.get('[data-testid="flight-search-form"]').within(() => {
          cy.get('[name="origin"]').type('LHR');
          cy.get('[name="destination"]').type('JFK');
          cy.get('[name="departure_date"]').type('2022-01-01');
          cy.get('button[type="submit"]').click();
        });
    
        cy.get('[data-testid="flight-list"]').within(() => {
          cy.get(':nth-child(1)').within(() => {
            cy.contains('Book now').click();
          });
        });
    
        cy.get('[data-testid="booking-form"]').within(() => {
          cy.get('[name="name"]').type('John Doe');
          cy.get('[name="email"]').type('[email protected]');
          cy.get('button[type="submit"]').click();
        });
    
        cy.contains('Your booking has been confirmed');
      });
    });
    

    This end-to-end test simulates a user searching for a flight from London (LHR) to New York (JFK) on January 1, 2022, selecting a flight, filling out the booking form, and then confirming the booking. If there are any issues or bugs with this flow, this end-to-end test should help identify them.

    Acceptance testing

    Acceptance testing is the process of testing your application against your business requirements and user expectations.

    This type of testing is often performed manually by a user or a testing team to ensure that your application meets the necessary criteria. Acceptance testing can help ensure that your application is fit for purpose and meets the needs of your users.

    For example, let's say we have a requirement that our flight booking application must be accessible to users with visual impairments:

    An acceptance test for this requirement might involve testing the application using a screen reader, such as NVDA, to ensure that all important information is read out loud and that the application is easy to use for users with visual impairments.

    Conclusion

    Frontend testing is a vital part of any modern web development project. By understanding the different types of frontend testing available, you can ensure that your code is tested thoroughly and that your application meets the necessary criteria.

    Whether you are writing unit tests, integration tests, end-to-end tests, or acceptance tests, the goal is always the same: to identify any issues or bugs early in the development process and ensure that your application is ready for production.

    © 2023 Designed & Developed by José Matos.