DEV Community

Cover image for Node.js E2E Testing with Cypress: A Comprehensive Setup Guide
Sivantha Paranavithana
Sivantha Paranavithana

Posted on

Node.js E2E Testing with Cypress: A Comprehensive Setup Guide

Introduction

End-to-end testing is crucial for ensuring your Node.js applications work flawlessly from the user's perspective. In this comprehensive guide, we'll walk through setting up Cypress for e2e testing in a Node.js Express project, complete with code coverage, Docker-based test databases, and best practices for organizing your test suites.

What You'll Learn 🎯

  • Setting up Cypress with Node.js and Express
  • Configuring test databases using Docker
  • Implementing code coverage with NYC
  • Organizing test suites effectively
  • Writing maintainable test cases
  • TypeScript integration
  • Best practices for e2e testing

Prerequisites

Before we dive in, make sure you have:

  • Node.js installed (v14 or later)
  • Basic understanding of Express.js
  • Docker installed (for test databases)
  • Your favorite code editor (VSCode recommended)

Setting Up Your Testing Environment

1. Installing Essential Dependencies

First, let's install the necessary packages:

yarn add -D cypress
yarn add -D nyc
yarn add -D ts-node
yarn add -D @cypress/code-coverage
Enter fullscreen mode Exit fullscreen mode

2. Docker-Based Test Database Setup

For consistent testing environments, we'll use Docker. Here's how to set up both MySQL and MongoDB:

MySQL Configuration:

# docker-compose.yml
services:
  test-db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
      MYSQL_DATABASE: ${DB_NAME}
    ports:
      - ${DB_PORT}:3306
    command: --default-authentication-plugin=mysql_native_password
    healthcheck:
      test: ['CMD', 'mysqladmin', 'ping', '-h', 'localhost']
      timeout: 20s
      retries: 10
Enter fullscreen mode Exit fullscreen mode

MongoDB Configuration:

# docker-compose.yml
services:
  mongodb:
    image: mongo:latest
    command: mongod --replSet rs0 --bind_ip_all
    environment:
      MONGO_INITDB_DATABASE: ${DB_NAME}
    ports:
      - ${DB_PORT}:27017
    healthcheck:
      test: mongosh --eval "try { rs.status() } catch (err) { rs.initiate() }"
      interval: 10s
      timeout: 10s
      retries: 5
Enter fullscreen mode Exit fullscreen mode

3. Environment Configuration

Create a .env.test file for your test environment:

PORT=3000
ENV="test"
PACKAGE_NAME="my-api"
DB_USERNAME="root"
DB_PASSWORD="root"
DB_HOST="127.0.0.1"
DB_PORT=3300
DB_NAME="test-db"
Enter fullscreen mode Exit fullscreen mode

Configuring Code Coverage with NYC

NYC (Istanbul) provides detailed code coverage metrics. Here's how to set it up:

// .nycrc
{
  "all": true,
  "include": ["src/**/*.ts"],
  "exclude": [
    "**/*.spec.ts",
    "**/*.test.ts",
    "**/*.cy.ts",
    "coverage/**"
  ],
  "extension": [".ts"],
  "reporter": ["json", "lcov", "text", "html"],
  "report-dir": "coverage/cypress",
  "temp-dir": ".nyc_output"
}
Enter fullscreen mode Exit fullscreen mode

Cypress Configuration

Create a cypress.config.ts file to configure Cypress with code coverage:

import { defineConfig } from 'cypress';
import path from 'path';
import environment from './src/config/env.config';

export default defineConfig({
  e2e: {
    setupNodeEvents(on, config) {
      require('@cypress/code-coverage/task')(on, config);
      return config;
    },
    baseUrl: `http://localhost:${environment.port}/api`,
    env: {
      packageName: environment.packageName,
      packageVersion: environment.packageVersion,
      env: environment.env,
      apiKey: environment.apiKey,
      dbURI: environment.dbURI,
      codeCoverage: {
        url: `http://localhost:${environment.port}/__coverage__`,
        expectBackendCoverageOnly: true,
      },
    },
    defaultCommandTimeout: 5000,
  },
  screenshotOnRunFailure: false,
  video: false,
});
Enter fullscreen mode Exit fullscreen mode

Organizing Your Cypress Tests

Project Structure

Here's a clean, maintainable structure for your Cypress tests:

cypress/
├── constants/          # Test data and constants
├── e2e/               # Test files
├── plugins/           # Cypress plugins
├── support/           # Commands and types
└── tsconfig.json      # TypeScript config
Enter fullscreen mode Exit fullscreen mode

Writing Effective Test Suites

Let's look at a real-world example of a well-structured test suite:

describe('POST /v1/users', () => {
  const { apiKey, dbURI } = Cypress.env();

  beforeEach(() => {
    cy.task('cleanupUsers', { dbURI });
  });

  it('should create a user with required fields', () => {
    cy.request({
      method: 'POST',
      url: '/v1/users',
      headers: { 'x-api-key': apiKey },
      body: {
        name: 'John Doe',
        email: 'john@example.com'
      }
    }).then((response) => {
      expect(response.status).to.eq(201);
      expect(response.body).to.have.property('id');
    });
  });

  // More test cases...
});
Enter fullscreen mode Exit fullscreen mode

Custom Commands

Create reusable test operations:

// commands/users.commands.ts
Cypress.Commands.add('createUser', (userData) => {
  return cy.request({
    method: 'POST',
    url: '/v1/users',
    headers: { 'x-api-key': Cypress.env('apiKey') },
    body: userData,
    failOnStatusCode: false,
  });
});
Enter fullscreen mode Exit fullscreen mode

Best Practices for E2E Testing

  1. Clean State for Each Test

    • Use beforeEach hooks to reset the database
    • Avoid test interdependence
  2. Organized Test Structure

    • Group related tests in describe blocks
    • Use meaningful test descriptions
    • Follow the AAA pattern (Arrange, Act, Assert)
  3. Error Handling

    • Test both success and failure scenarios
    • Validate error messages and status codes
    • Check edge cases
  4. Performance Considerations

    • Use appropriate timeouts
    • Minimize database operations
    • Implement parallel test execution when possible

Common Pitfalls and Solutions

  1. Database Connection Issues

    • Always check your connection strings
    • Implement proper error handling
    • Use connection pooling
  2. Flaky Tests

    • Add proper wait conditions
    • Don't rely on arbitrary timeouts
    • Use Cypress's built-in retry mechanism

TypeScript Integration

Ensure your tsconfig.json includes these essential compiler options:

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["es5", "dom"],
    "types": ["cypress", "node"],
    "esModuleInterop": true,
    "sourceMap": true
  },
  "include": ["**/*.ts"]
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

Setting up end-to-end testing with Cypress in your Node.js applications might seem daunting at first, but with proper organization and best practices, it becomes a powerful tool in your testing arsenal. Remember to:

  • Keep your test environment consistent
  • Maintain clean and organized test code
  • Follow best practices for reliable tests
  • Regular monitoring of code coverage

Resources

What's Next?

  • Implementing CI/CD with Cypress
  • Advanced test patterns
  • Visual regression testing
  • API mocking strategies

Happy testing! 🚀

Top comments (0)