• Tejaya's Blog
  • Posts
  • Automated Testing Best Practices: Jest, Cypress, and Playwright

Automated Testing Best Practices: Jest, Cypress, and Playwright

In modern application development, automated testing ensures high-quality software delivery. This article explores Jest for unit testing, Cypress for end-to-end (E2E) testing, and Playwright for cross-browser testing. We'll build a small React application and integrate all three tools.

Project Overview

We’ll create a To-Do App with the following functionality:

  1. Unit Tests with Jest for utility functions.

  2. Component Tests with Jest and React Testing Library.

  3. End-to-End Tests with Cypress.

  4. Cross-Browser Testing with Playwright.

1. Setting Up the Project

Step 1: Initialize React App

Create a React app:

npx create-react-app todo-app
cd todo-app
npm install

Step 2: Install Testing Libraries

Install required dependencies:

npm install --save-dev jest @testing-library/react @testing-library/jest-dom cypress playwright

2. To-Do App Implementation

Here’s the full code for the To-Do App:

src/utils/todoUtils.js

Utility functions for managing tasks:

export const addTodo = (todos, newTodo) => {
  return [...todos, { id: todos.length + 1, task: newTodo, completed: false }];
};

export const toggleTodo = (todos, id) => {
  return todos.map((todo) =>
    todo.id === id ? { ...todo, completed: !todo.completed } : todo
  );
};

src/App.js

React application using the utility functions

import React, { useState } from "react";
import { addTodo, toggleTodo } from "./utils/todoUtils";

// Reusable Todo component
const Todo = ({ todo, onToggle }) => (
  <li>
    <label>
      <input
        type="checkbox"
        checked={todo.completed}
        onChange={() => onToggle(todo.id)}
      />
      {todo.task}
    </label>
  </li>
);

const App = () => {
  const [todos, setTodos] = useState([]);
  const [task, setTask] = useState("");

  const handleAddTodo = () => {
    if (task.trim()) {
      const updatedTodos = addTodo(todos, task);
      setTodos(updatedTodos);
      setTask("");
    }
  };

  const handleToggleTodo = (id) => {
    const updatedTodos = toggleTodo(todos, id);
    setTodos(updatedTodos);
  };

  return (
    <div style={{ padding: "20px" }}>
      <h1>To-Do App</h1>
      <div>
        <input
          type="text"
          placeholder="Add a task"
          value={task}
          onChange={(e) => setTask(e.target.value)}
        />
        <button onClick={handleAddTodo}>Add</button>
      </div>
      <ul>
        {todos.map((todo) => (
          <Todo key={todo.id} todo={todo} onToggle={handleToggleTodo} />
        ))}
      </ul>
    </div>
  );
};

export default App;

3. Unit Tests with Jest

Test the utility functions:

src/utils/todoUtils.test.js

import { addTodo, toggleTodo } from './todoUtils';

describe('Todo Utils', () => {
  it('should add a new todo', () => {
    const todos = [];
    const result = addTodo(todos, 'New Task');
    expect(result).toHaveLength(1);
    expect(result[0]).toMatchObject({ task: 'New Task', completed: false });
  });

  it('should toggle a todo\'s completed status', () => {
    const todos = [{ id: 1, task: 'Task 1', completed: false }];
    const result = toggleTodo(todos, 1);
    expect(result[0].completed).toBe(true);
  });
});

Run the tests

npm test

4. Component Tests with Jest and React Testing Library

src/App.test.js

import { render, screen, fireEvent } from '@testing-library/react';
import App from './App';

test('renders the To-Do App and adds a task', () => {
  render(<App />);
  
  const input = screen.getByPlaceholderText('Add a task');
  const button = screen.getByText('Add');

  fireEvent.change(input, { target: { value: 'New Task' } });
  fireEvent.click(button);

  expect(screen.getByText('New Task')).toBeInTheDocument();
});

test('toggles the task completed status', () => {
  render(<App />);
  
  const input = screen.getByPlaceholderText('Add a task');
  const button = screen.getByText('Add');

  fireEvent.change(input, { target: { value: 'New Task' } });
  fireEvent.click(button);

  const checkbox = screen.getByRole('checkbox');
  fireEvent.click(checkbox);

  expect(checkbox).toBeChecked();
});

5. End-to-End Tests with Cypress

cypress/e2e/todoApp.cy.js

describe('To-Do App', () => {
  it('should add a new to-do', () => {
    cy.visit('/');
    cy.get('input[placeholder="Add a task"]').type('New Task{enter}');
    cy.contains('New Task').should('exist');
  });

  it('should toggle a task', () => {
    cy.visit('/');
    cy.get('input[placeholder="Add a task"]').type('New Task{enter}');
    cy.get('input[type="checkbox"]').first().check();
    cy.get('input[type="checkbox"]').first().should('be.checked');
  });
});

Run the tests

npx cypress open

6. Cross-Browser Testing with Playwright

tests/todoApp.spec.js

const { test, expect } = require('@playwright/test');

test('should add and toggle a to-do', async ({ page }) => {
  await page.goto('http://localhost:3000');
  await page.fill('input[placeholder="Add a task"]', 'New Task');
  await page.keyboard.press('Enter');
  
  await expect(page.locator('text=New Task')).toBeVisible();

  const checkbox = page.locator('input[type="checkbox"]');
  await checkbox.check();
  await expect(checkbox).toBeChecked();
});

Run Playwright tests

npx playwright test

7. Best Practices

  • Organize Tests: Separate unit, component, and E2E tests into folders.

  • Mock API Calls: Use tools like msw for consistent testing.

  • CI Integration: Run tests automatically in CI/CD pipelines.

Thank you for reading this guide on integrating Jest, Cypress, and Playwright for automated testing in modern apps. I hope you found it insightful and useful for your projects. If you enjoyed this article and want more hands-on tutorials, tips, and updates on modern software development practices, consider subscribing to my newsletter! Stay ahead with the latest trends and best practices delivered straight to your inbox.

Subscribe here and join the journey of mastering cutting-edge tech! 🚀

Reply

or to participate.