- 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:
Unit Tests with Jest for utility functions.
Component Tests with Jest and React Testing Library.
End-to-End Tests with Cypress.
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