๐Ÿ—๏ธ Full-Stack Testing Suite

Complete Testing Strategy with Jest, Mocha & Cypress

Jest Mocha Cypress React Node.js Full-Stack

๐Ÿ“‹ Project Overview

This comprehensive project demonstrates a complete full-stack testing strategy using three different testing frameworks: Jest for frontend unit tests, Mocha for backend API tests, and Cypress for end-to-end testing.

By combining these three frameworks, we achieve complete test coverage across all application layers, following the testing pyramid principle. Each framework is used where it excels most, providing fast, reliable, and maintainable test suites.

๐Ÿ“Š Testing Coverage Overview

127
Total Tests
97%
Code Coverage
3
Test Frameworks
<5s
Suite Runtime

๐Ÿ”บ Testing Pyramid Strategy

Following the testing pyramid principle for optimal coverage and speed

E2E Tests
Cypress
15 tests โ€ข Slow โ€ข High Confidence
Integration Tests
Mocha + Chai
42 tests โ€ข Medium โ€ข API Testing
Unit Tests
Jest + RTL
70 tests โ€ข Fast โ€ข Component Testing

๐Ÿ”ง Testing Frameworks Breakdown

๐Ÿƒ
Jest
Frontend Unit Testing
  • React component testing
  • Hooks and state management
  • Utility functions
  • Snapshot testing
  • Fast execution (<2s)
โ˜•
Mocha
Backend API Testing
  • RESTful API endpoints
  • Database operations
  • Business logic validation
  • Error handling
  • BDD-style specs
๐ŸŒฒ
Cypress
End-to-End Testing
  • Complete user flows
  • Cross-component integration
  • Real browser testing
  • Visual regression
  • Network stubbing

๐Ÿƒ Jest - Frontend Unit Tests

Testing React components, hooks, and utility functions with Jest and React Testing Library:

Component Test Example

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

describe('TaskItem Component', () => {
  const mockTask = {
    id: 1,
    title: 'Complete project',
    completed: false
  };

  it('should render task title', () => {
    render(<TaskItem task={mockTask} />);
    expect(screen.getByText('Complete project')).toBeInTheDocument();
  });

  it('should toggle task completion on click', () => {
    const onToggle = jest.fn();
    render(<TaskItem task={mockTask} onToggle={onToggle} />);
    
    const checkbox = screen.getByRole('checkbox');
    fireEvent.click(checkbox);
    
    expect(onToggle).toHaveBeenCalledWith(1);
  });

  it('should apply completed style when task is done', () => {
    const completedTask = { ...mockTask, completed: true };
    render(<TaskItem task={completedTask} />);
    
    const title = screen.getByText('Complete project');
    expect(title).toHaveClass('task-completed');
  });
});

Custom Hook Test

// useTasks.test.js
import { renderHook, act } from '@testing-library/react';
import useTasks from './useTasks';

describe('useTasks Hook', () => {
  it('should add new task', () => {
    const { result } = renderHook(() => useTasks());
    
    act(() => {
      result.current.addTask('New Task');
    });
    
    expect(result.current.tasks).toHaveLength(1);
    expect(result.current.tasks[0].title).toBe('New Task');
  });

  it('should filter completed tasks', () => {
    const { result } = renderHook(() => useTasks());
    
    act(() => {
      result.current.addTask('Task 1');
      result.current.addTask('Task 2');
      result.current.toggleTask(result.current.tasks[0].id);
    });
    
    const completed = result.current.getCompletedTasks();
    expect(completed).toHaveLength(1);
  });
});

โ˜• Mocha - Backend API Tests

Testing RESTful API endpoints with Mocha, Chai, and Supertest:

API Endpoint Tests

// tasks.api.test.js
const { expect } = require('chai');
const request = require('supertest');
const app = require('../app');

describe('Tasks API', function() {
  
  describe('GET /api/tasks', function() {
    it('should return all tasks', async function() {
      const response = await request(app)
        .get('/api/tasks')
        .expect(200);
      
      expect(response.body).to.be.an('array');
      expect(response.body).to.have.lengthOf.at.least(0);
    });

    it('should filter tasks by status', async function() {
      const response = await request(app)
        .get('/api/tasks?status=completed')
        .expect(200);
      
      expect(response.body.every(task => 
        task.completed === true
      )).to.be.true;
    });
  });

  describe('POST /api/tasks', function() {
    it('should create a new task', async function() {
      const newTask = {
        title: 'Test Task',
        description: 'Test Description'
      };

      const response = await request(app)
        .post('/api/tasks')
        .send(newTask)
        .expect(201);
      
      expect(response.body).to.include(newTask);
      expect(response.body).to.have.property('id');
      expect(response.body.completed).to.be.false;
    });

    it('should validate required fields', async function() {
      const response = await request(app)
        .post('/api/tasks')
        .send({})
        .expect(400);
      
      expect(response.body.error).to.match(/title.*required/i);
    });
  });
});

Database Integration Test

// database.test.js
const { expect } = require('chai');
const TaskRepository = require('../repositories/TaskRepository');

describe('Task Repository', function() {
  
  beforeEach(async function() {
    await TaskRepository.deleteAll();
  });

  it('should save and retrieve task', async function() {
    const task = await TaskRepository.create({
      title: 'Database Test'
    });
    
    const retrieved = await TaskRepository.findById(task.id);
    expect(retrieved.title).to.equal('Database Test');
  });

  it('should update task status', async function() {
    const task = await TaskRepository.create({
      title: 'Update Test'
    });
    
    await TaskRepository.update(task.id, { completed: true });
    const updated = await TaskRepository.findById(task.id);
    
    expect(updated.completed).to.be.true;
  });
});

๐ŸŒฒ Cypress - End-to-End Tests

Testing complete user flows and cross-component integration:

User Flow Test

// tasks.e2e.cy.js
describe('Task Management Flow', () => {
  beforeEach(() => {
    cy.visit('/');
  });

  it('should complete full task lifecycle', () => {
    // Create new task
    cy.get('[data-cy="task-input"]')
      .type('Buy groceries');
    cy.get('[data-cy="add-task-btn"]')
      .click();
    
    // Verify task appears
    cy.get('[data-cy="task-list"]')
      .should('contain', 'Buy groceries');
    
    // Mark as complete
    cy.get('[data-cy="task-item"]')
      .first()
      .find('[data-cy="checkbox"]')
      .click();
    
    // Verify completed state
    cy.get('[data-cy="task-item"]')
      .first()
      .should('have.class', 'completed');
    
    // Delete task
    cy.get('[data-cy="delete-btn"]')
      .first()
      .click();
    cy.get('[data-cy="confirm-delete"]')
      .click();
    
    // Verify task removed
    cy.get('[data-cy="task-list"]')
      .should('not.contain', 'Buy groceries');
  });

  it('should filter tasks by status', () => {
    // Add multiple tasks
    const tasks = ['Task 1', 'Task 2', 'Task 3'];
    tasks.forEach(task => {
      cy.get('[data-cy="task-input"]').type(task);
      cy.get('[data-cy="add-task-btn"]').click();
    });
    
    // Complete one task
    cy.get('[data-cy="task-item"]')
      .first()
      .find('[data-cy="checkbox"]')
      .click();
    
    // Filter to show only active
    cy.get('[data-cy="filter-active"]').click();
    cy.get('[data-cy="task-item"]').should('have.length', 2);
    
    // Filter to show completed
    cy.get('[data-cy="filter-completed"]').click();
    cy.get('[data-cy="task-item"]').should('have.length', 1);
  });
});

API Stubbing Test

// api-stubbing.cy.js
describe('Task API Integration', () => {
  it('should handle API errors gracefully', () => {
    // Stub API to return error
    cy.intercept('POST', '/api/tasks', {
      statusCode: 500,
      body: { error: 'Server error' }
    }).as('createTask');
    
    cy.visit('/');
    cy.get('[data-cy="task-input"]').type('Test Task');
    cy.get('[data-cy="add-task-btn"]').click();
    
    cy.wait('@createTask');
    
    // Verify error message shown
    cy.get('[data-cy="error-message"]')
      .should('be.visible')
      .and('contain', 'Failed to create task');
  });

  it('should load tasks from API', () => {
    const mockTasks = [
      { id: 1, title: 'Mock Task 1', completed: false },
      { id: 2, title: 'Mock Task 2', completed: true }
    ];
    
    cy.intercept('GET', '/api/tasks', mockTasks).as('getTasks');
    
    cy.visit('/');
    cy.wait('@getTasks');
    
    cy.get('[data-cy="task-item"]').should('have.length', 2);
    cy.get('[data-cy="task-list"]').should('contain', 'Mock Task 1');
  });
});

๐ŸŽฏ Why Use Three Frameworks Together?

โšก Speed & Efficiency

Unit tests (Jest) run in milliseconds, providing instant feedback during development. Integration tests (Mocha) run in seconds. E2E tests (Cypress) run slower but catch integration issues.

๐ŸŽฏ Right Tool, Right Job

Each framework excels in its domain: Jest for React components, Mocha for backend APIs with flexible assertions, Cypress for real user interactions.

๐Ÿ“ˆ Comprehensive Coverage

Combined coverage across all layers ensures no gaps. Unit tests catch logic errors, integration tests catch API issues, E2E tests catch UX problems.

๐Ÿ”„ Continuous Integration

Fast unit tests run on every commit, integration tests on pull requests, E2E tests before deployment. Optimized CI/CD pipeline.

โš™๏ธ Test Execution Strategy

Development Workflow

1

Write Code

Develop new feature or fix bug

โ†’
2

Unit Tests (Jest)

Test individual components - runs in <2s

โ†’
3

Integration Tests (Mocha)

Test API endpoints - runs in <5s

โ†’
4

E2E Tests (Cypress)

Test critical user flows before deploy

Package.json Scripts

{
  "scripts": {
    "test": "npm run test:unit && npm run test:integration && npm run test:e2e",
    "test:unit": "jest --coverage",
    "test:unit:watch": "jest --watch",
    "test:integration": "mocha test/integration/**/*.test.js",
    "test:integration:watch": "mocha --watch test/integration/**/*.test.js",
    "test:e2e": "cypress run",
    "test:e2e:open": "cypress open",
    "test:ci": "npm run test:unit && npm run test:integration"
  }
}

๐ŸŽฎ Interactive Task Management Demo

Try out the application that's being tested with all three frameworks:

My Tasks

0 total 0 completed

โš™๏ธ Configuration Files

Jest Configuration

// jest.config.js
module.exports = {
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],
  moduleNameMapper: {
    '\\.(css|less|scss)$': 'identity-obj-proxy',
  },
  collectCoverageFrom: [
    'src/**/*.{js,jsx}',
    '!src/index.js',
    '!src/reportWebVitals.js',
  ],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80,
    },
  },
};

Mocha Configuration

// .mocharc.json
{
  "require": ["./test/setup.js"],
  "spec": "test/integration/**/*.test.js",
  "timeout": 5000,
  "reporter": "spec",
  "ui": "bdd",
  "exit": true
}

Cypress Configuration

// cypress.config.js
const { defineConfig } = require('cypress');

module.exports = defineConfig({
  e2e: {
    baseUrl: 'http://localhost:3000',
    viewportWidth: 1280,
    viewportHeight: 720,
    video: true,
    screenshotOnRunFailure: true,
    setupNodeEvents(on, config) {
      // implement node event listeners here
    },
  },
});

๐Ÿ”„ CI/CD Pipeline Integration

GitHub Actions Workflow

name: Full-Stack Test Suite

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run Unit Tests (Jest)
        run: npm run test:unit
      
      - name: Run Integration Tests (Mocha)
        run: npm run test:integration
      
      - name: Run E2E Tests (Cypress)
        uses: cypress-io/github-action@v5
        with:
          start: npm start
          wait-on: 'http://localhost:3000'
      
      - name: Upload Coverage
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage/lcov.info
      
      - name: Upload Cypress Videos
        if: failure()
        uses: actions/upload-artifact@v3
        with:
          name: cypress-videos
          path: cypress/videos

๐ŸŽ“ Key Learnings & Benefits

๐Ÿ”ฌ

Comprehensive Testing Strategy

Understanding when to use unit, integration, and E2E tests. Each layer serves a specific purpose in the testing pyramid.

โšก

Optimized Test Performance

Fast feedback loop with unit tests, slower but thorough E2E tests. Balance between speed and confidence.

๐Ÿ› ๏ธ

Framework Expertise

Deep knowledge of Jest for React, Mocha for backend APIs, and Cypress for E2E. Each framework's strengths and trade-offs.

๐Ÿ“Š

Quality Metrics

Achieving 97% code coverage across all layers. Understanding meaningful coverage vs vanity metrics.

๐Ÿ”„

CI/CD Integration

Automated testing in pipelines. Fast tests on commits, full suite before deployment. Continuous quality assurance.

๐ŸŽฏ

Best Practices

Test isolation, mocking strategies, data-driven tests, and maintainable test code. Writing tests that add value.

๐Ÿ“ˆ Test Results Summary

Framework Test Count Coverage Execution Time Purpose
Jest 70 tests 95% <2s Frontend components & logic
Mocha 42 tests 98% ~3s Backend API & database
Cypress 15 tests N/A ~15s End-to-end user flows
TOTAL 127 tests 97% <5s Full-stack coverage

๐ŸŽฏ Conclusion

This full-stack testing suite demonstrates a production-ready testing strategy that combines the strengths of three powerful frameworks. By using Jest for fast unit tests, Mocha for flexible API testing, and Cypress for comprehensive E2E testing, we achieve optimal coverage with efficient execution times.

The key to successful testing isn't using every tool availableโ€”it's using the right tool for each job. This project showcases that understanding, providing a blueprint for scalable, maintainable test suites in modern full-stack applications.