+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 214 of 355

🧪 Testing Node.js Apps: Jest and Supertest

Master testing node.js apps: jest and supertest in TypeScript with practical examples, best practices, and real-world applications 🚀

🚀Intermediate
30 min read

Prerequisites

  • Basic understanding of JavaScript 📝
  • TypeScript installation ⚡
  • VS Code or preferred IDE 💻

What you'll learn

  • Understand Jest and Supertest fundamentals 🎯
  • Apply testing in real Node.js projects 🏗️
  • Debug common testing issues 🐛
  • Write type-safe tests ✨

🎯 Introduction

Welcome to the exciting world of Node.js testing! 🎉 In this guide, we’ll explore how to test your Node.js applications using Jest and Supertest with TypeScript.

Testing isn’t just about finding bugs 🐛 - it’s about building confidence in your code! You’ll discover how proper testing can transform your development experience, making deployments stress-free and refactoring fearless. Whether you’re building REST APIs 🌐, microservices 🚀, or complex backend systems 🖥️, understanding testing is essential for writing robust, maintainable applications.

By the end of this tutorial, you’ll feel confident writing comprehensive tests for your Node.js apps! Let’s dive in! 🏊‍♂️

📚 Understanding Jest and Supertest

🤔 What are Jest and Supertest?

Think of Jest as your testing command center 🎛️ and Supertest as your HTTP request inspector 🔍. Jest is like having a personal assistant that runs all your tests, provides beautiful reports, and catches issues before they reach production. Supertest is like having a quality assurance expert who tests every API endpoint to make sure they work perfectly!

In TypeScript terms, Jest provides the testing framework with assertions, mocks, and test runners ✨, while Supertest gives you tools to test HTTP endpoints without starting a real server 🚀. This means you can:

  • ✨ Test your code in isolation
  • 🚀 Run thousands of tests in seconds
  • 🛡️ Catch bugs before users do
  • 📖 Document expected behavior

💡 Why Use Jest and Supertest?

Here’s why developers love this testing combo:

  1. Type Safety 🔒: Full TypeScript support with type checking
  2. Speed ⚡: Lightning-fast test execution
  3. Simplicity 🎯: Intuitive API that’s easy to learn
  4. Powerful Features 💪: Mocking, snapshots, coverage reports
  5. API Testing 🌐: Test HTTP endpoints without complexity

Real-world example: Imagine building an e-commerce API 🛒. With Jest and Supertest, you can test user registration, product searches, and checkout processes without actually processing payments or sending emails!

🔧 Basic Syntax and Usage

📝 Setting Up Your Testing Environment

Let’s start by setting up our testing toolkit:

# 📦 Install testing dependencies
npm install --save-dev jest @types/jest supertest @types/supertest ts-jest

# 🎯 Or with pnpm (recommended for this project)
pnpm add -D jest @types/jest supertest @types/supertest ts-jest
// 🛠️ jest.config.js - Your testing configuration
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  roots: ['<rootDir>/src'],
  testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
  collectCoverageFrom: [
    'src/**/*.ts',
    '!src/**/*.d.ts',
  ],
  coverageDirectory: 'coverage',
  coverageReporters: ['text', 'lcov', 'html'],
};

🎯 Your First Test

Here’s a simple test to get you started:

// 📁 src/utils/calculator.ts
export class Calculator {
  // ➕ Add two numbers
  add(a: number, b: number): number {
    return a + b;
  }

  // ➖ Subtract two numbers  
  subtract(a: number, b: number): number {
    return a - b;
  }

  // ✖️ Multiply two numbers
  multiply(a: number, b: number): number {
    return a * b;
  }

  // ➗ Divide two numbers (with safety check!)
  divide(a: number, b: number): number {
    if (b === 0) {
      throw new Error('Cannot divide by zero! 🚫');
    }
    return a / b;
  }
}
// 📁 src/utils/__tests__/calculator.test.ts
import { Calculator } from '../calculator';

describe('🧮 Calculator', () => {
  let calculator: Calculator;

  // 🏗️ Set up before each test
  beforeEach(() => {
    calculator = new Calculator();
  });

  describe('➕ Addition', () => {
    it('should add two positive numbers correctly', () => {
      // 🎯 Arrange
      const a = 5;
      const b = 3;

      // 🚀 Act
      const result = calculator.add(a, b);

      // ✅ Assert
      expect(result).toBe(8);
      expect(result).toBeDefined();
      expect(typeof result).toBe('number');
    });

    it('should handle negative numbers', () => {
      // 🎯 Testing edge cases is important!
      expect(calculator.add(-5, 3)).toBe(-2);
      expect(calculator.add(-5, -3)).toBe(-8);
    });
  });

  describe('➗ Division', () => {
    it('should divide numbers correctly', () => {
      expect(calculator.divide(10, 2)).toBe(5);
      expect(calculator.divide(7, 2)).toBe(3.5);
    });

    it('should throw error when dividing by zero', () => {
      // 🚫 Testing error cases
      expect(() => calculator.divide(10, 0))
        .toThrow('Cannot divide by zero! 🚫');
    });
  });
});

💡 Explanation: Notice how we organize tests with describe blocks for grouping and it blocks for individual test cases. The beforeEach ensures each test starts fresh!

💡 Practical Examples

🛒 Example 1: Testing an E-commerce API

Let’s test a real shopping cart API:

// 📁 src/models/Product.ts
export interface Product {
  id: string;
  name: string;
  price: number;
  category: string;
  inStock: boolean;
  emoji: string; // 🎨 Every product needs personality!
}

// 📁 src/services/ProductService.ts
export class ProductService {
  private products: Product[] = [
    { id: '1', name: 'TypeScript Book', price: 29.99, category: 'books', inStock: true, emoji: '📘' },
    { id: '2', name: 'Coffee Mug', price: 12.99, category: 'lifestyle', inStock: true, emoji: '☕' },
    { id: '3', name: 'Laptop Sticker', price: 4.99, category: 'tech', inStock: false, emoji: '💻' }
  ];

  // 🔍 Find product by ID
  async findById(id: string): Promise<Product | null> {
    const product = this.products.find(p => p.id === id);
    return product || null;
  }

  // 📋 Get all products
  async findAll(): Promise<Product[]> {
    return this.products;
  }

  // 🏷️ Filter by category
  async findByCategory(category: string): Promise<Product[]> {
    return this.products.filter(p => p.category === category);
  }

  // ✅ Check if product is available
  async isAvailable(id: string): Promise<boolean> {
    const product = await this.findById(id);
    return product ? product.inStock : false;
  }
}
// 📁 src/routes/products.ts
import express from 'express';
import { ProductService } from '../services/ProductService';

const router = express.Router();
const productService = new ProductService();

// 📋 GET /products - List all products
router.get('/', async (req, res) => {
  try {
    const products = await productService.findAll();
    res.json({
      success: true,
      data: products,
      message: `Found ${products.length} amazing products! 🛍️`
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      message: 'Oops! Something went wrong 😅'
    });
  }
});

// 🔍 GET /products/:id - Get specific product
router.get('/:id', async (req, res) => {
  try {
    const product = await productService.findById(req.params.id);
    
    if (!product) {
      return res.status(404).json({
        success: false,
        message: 'Product not found 😢'
      });
    }

    res.json({
      success: true,
      data: product,
      message: `Here's your ${product.emoji} ${product.name}!`
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      message: 'Error fetching product 💥'
    });
  }
});

export { router as productsRouter };

Now let’s test this API with Supertest:

// 📁 src/routes/__tests__/products.test.ts
import request from 'supertest';
import express from 'express';
import { productsRouter } from '../products';

// 🏗️ Create test app
const app = express();
app.use(express.json());
app.use('/products', productsRouter);

describe('🛍️ Products API', () => {
  describe('GET /products', () => {
    it('should return all products with success message', async () => {
      // 🚀 Make the request
      const response = await request(app)
        .get('/products')
        .expect(200)
        .expect('Content-Type', /json/);

      // ✅ Verify the response
      expect(response.body.success).toBe(true);
      expect(response.body.data).toHaveLength(3);
      expect(response.body.message).toContain('Found 3 amazing products!');
      
      // 🎯 Check first product structure
      const firstProduct = response.body.data[0];
      expect(firstProduct).toHaveProperty('id');
      expect(firstProduct).toHaveProperty('name');
      expect(firstProduct).toHaveProperty('price');
      expect(firstProduct).toHaveProperty('emoji');
    });
  });

  describe('GET /products/:id', () => {
    it('should return specific product when found', async () => {
      const response = await request(app)
        .get('/products/1')
        .expect(200);

      expect(response.body.success).toBe(true);
      expect(response.body.data.id).toBe('1');
      expect(response.body.data.name).toBe('TypeScript Book');
      expect(response.body.data.emoji).toBe('📘');
      expect(response.body.message).toContain('Here\'s your 📘 TypeScript Book!');
    });

    it('should return 404 when product not found', async () => {
      const response = await request(app)
        .get('/products/999')
        .expect(404);

      expect(response.body.success).toBe(false);
      expect(response.body.message).toBe('Product not found 😢');
    });

    it('should handle invalid product IDs gracefully', async () => {
      const response = await request(app)
        .get('/products/invalid-id')
        .expect(404);

      expect(response.body.success).toBe(false);
    });
  });
});

🎮 Example 2: Testing a Game Score API

Let’s test a more complex example with authentication:

// 📁 src/middleware/auth.ts
import { Request, Response, NextFunction } from 'express';

export interface AuthenticatedRequest extends Request {
  user?: {
    id: string;
    username: string;
  };
}

export const authMiddleware = (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
  const token = req.headers.authorization?.replace('Bearer ', '');
  
  if (!token) {
    return res.status(401).json({
      success: false,
      message: 'Authentication required! 🔐'
    });
  }

  // 🎯 Simple token validation (in real app, use JWT)
  if (token === 'valid-token') {
    req.user = { id: '1', username: 'player1' };
    next();
  } else {
    res.status(401).json({
      success: false,
      message: 'Invalid token! 🚫'
    });
  }
};
// 📁 src/routes/__tests__/game-scores.test.ts
import request from 'supertest';
import express from 'express';
import { gameScoresRouter } from '../game-scores';

const app = express();
app.use(express.json());
app.use('/scores', gameScoresRouter);

describe('🎮 Game Scores API', () => {
  const validToken = 'valid-token';
  const invalidToken = 'invalid-token';

  describe('🔐 Authentication', () => {
    it('should reject requests without token', async () => {
      const response = await request(app)
        .post('/scores')
        .send({ score: 100, level: 1 })
        .expect(401);

      expect(response.body.message).toBe('Authentication required! 🔐');
    });

    it('should reject requests with invalid token', async () => {
      const response = await request(app)
        .post('/scores')
        .set('Authorization', `Bearer ${invalidToken}`)
        .send({ score: 100, level: 1 })
        .expect(401);

      expect(response.body.message).toBe('Invalid token! 🚫');
    });
  });

  describe('📊 Score Submission', () => {
    it('should accept valid score submission', async () => {
      const scoreData = {
        score: 1500,
        level: 5,
        achievements: ['🌟 First Steps', '🏆 Level 5 Master']
      };

      const response = await request(app)
        .post('/scores')
        .set('Authorization', `Bearer ${validToken}`)
        .send(scoreData)
        .expect(201);

      expect(response.body.success).toBe(true);
      expect(response.body.data.score).toBe(1500);
      expect(response.body.data.level).toBe(5);
      expect(response.body.message).toContain('score saved');
    });

    it('should validate score data', async () => {
      const invalidScore = {
        score: -100, // 🚫 Negative scores not allowed
        level: 0     // 🚫 Level must be positive
      };

      const response = await request(app)
        .post('/scores')
        .set('Authorization', `Bearer ${validToken}`)
        .send(invalidScore)
        .expect(400);

      expect(response.body.success).toBe(false);
      expect(response.body.message).toContain('validation');
    });
  });
});

🚀 Advanced Testing Concepts

🧙‍♂️ Mocking External Dependencies

When testing gets complex, mocking is your best friend:

// 📁 src/services/__tests__/EmailService.test.ts
import { EmailService } from '../EmailService';
import { DatabaseService } from '../DatabaseService';

// 🎭 Mock the database service
jest.mock('../DatabaseService');
const mockDatabaseService = DatabaseService as jest.Mocked<typeof DatabaseService>;

describe('📧 Email Service', () => {
  let emailService: EmailService;
  let mockDb: jest.Mocked<DatabaseService>;

  beforeEach(() => {
    // 🔄 Fresh mocks for each test
    mockDb = new mockDatabaseService() as jest.Mocked<DatabaseService>;
    emailService = new EmailService(mockDb);
  });

  it('should send welcome email to new users', async () => {
    // 🎯 Arrange
    const userData = { id: '1', email: '[email protected]', name: 'Alice' };
    mockDb.saveUser.mockResolvedValue(userData);
    
    // 🎭 Mock the email sending
    const sendEmailSpy = jest.spyOn(emailService, 'sendEmail')
      .mockResolvedValue({ success: true, messageId: 'msg-123' });

    // 🚀 Act
    await emailService.sendWelcomeEmail(userData);

    // ✅ Assert
    expect(sendEmailSpy).toHaveBeenCalledWith(
      userData.email,
      'Welcome to our app! 🎉',
      expect.stringContaining('Hi Alice')
    );
    expect(mockDb.saveUser).toHaveBeenCalledWith(userData);
  });
});

🏗️ Testing Async Operations

// 📁 src/services/__tests__/PaymentService.test.ts
describe('💳 Payment Service', () => {
  it('should process payment within timeout', async () => {
    const paymentData = {
      amount: 99.99,
      currency: 'USD',
      cardToken: 'tok_123'
    };

    // ⏰ Test with timeout
    const promise = paymentService.processPayment(paymentData);
    
    // 🏃‍♂️ Should complete within 5 seconds
    await expect(promise).resolves.toMatchObject({
      success: true,
      transactionId: expect.any(String)
    });

    // ⏱️ Or test that it doesn't take too long
    const startTime = Date.now();
    await promise;
    const duration = Date.now() - startTime;
    
    expect(duration).toBeLessThan(5000); // Should be fast!
  }, 10000); // 10 second timeout for this test
});

⚠️ Common Pitfalls and Solutions

😱 Pitfall 1: Not Cleaning Up After Tests

// ❌ Wrong way - tests affect each other!
describe('Database Tests', () => {
  it('should create user', async () => {
    await db.createUser({ name: 'Alice' });
    const users = await db.getUsers();
    expect(users).toHaveLength(1);
  });

  it('should list users', async () => {
    const users = await db.getUsers();
    expect(users).toHaveLength(0); // 💥 Fails! Previous test left data
  });
});

// ✅ Correct way - clean slate for each test!
describe('Database Tests', () => {
  beforeEach(async () => {
    await db.clearAll(); // 🧹 Clean up before each test
  });

  afterEach(async () => {
    await db.clearAll(); // 🧹 Clean up after each test too
  });

  it('should create user', async () => {
    await db.createUser({ name: 'Alice' });
    const users = await db.getUsers();
    expect(users).toHaveLength(1);
  });

  it('should list users', async () => {
    const users = await db.getUsers();
    expect(users).toHaveLength(0); // ✅ Passes!
  });
});

🤯 Pitfall 2: Testing Implementation Instead of Behavior

// ❌ Testing implementation details
it('should call getUserById method', async () => {
  const spy = jest.spyOn(userService, 'getUserById');
  await userController.getUser('123');
  expect(spy).toHaveBeenCalledWith('123'); // 🚫 Too coupled to implementation
});

// ✅ Testing behavior and outcomes
it('should return user data when user exists', async () => {
  const userData = { id: '123', name: 'Alice', email: '[email protected]' };
  jest.spyOn(userService, 'getUserById').mockResolvedValue(userData);

  const response = await request(app)
    .get('/users/123')
    .expect(200);

  expect(response.body.data).toEqual(userData);
  expect(response.body.success).toBe(true);
});

🛠️ Best Practices

  1. 🎯 Test Behavior, Not Implementation: Focus on what your code does, not how it does it
  2. 📝 Descriptive Test Names: should return 404 when user not found not test user endpoint
  3. 🧹 Clean Tests: Use setup/teardown to keep tests isolated
  4. 🎭 Mock External Dependencies: Don’t let external services break your tests
  5. ⚡ Fast Tests: Keep unit tests under 1 second each
  6. 📊 Good Coverage: Aim for 80%+ but focus on critical paths
  7. 🔄 Test Edge Cases: Empty arrays, null values, boundary conditions

🧪 Hands-On Exercise

🎯 Challenge: Build a Blog API Test Suite

Create a comprehensive test suite for a blog API with the following features:

📋 Requirements:

  • ✅ User authentication (register, login, logout)
  • 📝 CRUD operations for blog posts
  • 💬 Comments system with moderation
  • 🏷️ Tagging and categories
  • 🔍 Search functionality
  • 👤 User roles (author, editor, admin)

🚀 Bonus Points:

  • Rate limiting tests
  • File upload tests for images
  • Email notification tests
  • Performance tests

💡 Test Categories to Include:

  • Unit tests for services and utilities
  • Integration tests for API endpoints
  • Authentication and authorization tests
  • Error handling and edge case tests

💡 Solution

🔍 Click to see solution
// 📁 src/models/Blog.ts
export interface BlogPost {
  id: string;
  title: string;
  content: string;
  authorId: string;
  published: boolean;
  tags: string[];
  createdAt: Date;
  updatedAt: Date;
  emoji: string;
}

export interface Comment {
  id: string;
  postId: string;
  authorId: string;
  content: string;
  approved: boolean;
  createdAt: Date;
}

// 📁 src/services/__tests__/BlogService.test.ts
import { BlogService } from '../BlogService';
import { DatabaseService } from '../DatabaseService';

jest.mock('../DatabaseService');

describe('📝 Blog Service', () => {
  let blogService: BlogService;
  let mockDb: jest.Mocked<DatabaseService>;

  beforeEach(() => {
    mockDb = new DatabaseService() as jest.Mocked<DatabaseService>;
    blogService = new BlogService(mockDb);
  });

  describe('📝 Post Creation', () => {
    it('should create new blog post with all fields', async () => {
      const postData = {
        title: 'My First TypeScript Blog Post 🚀',
        content: 'TypeScript is amazing for building scalable applications!',
        authorId: 'user-123',
        tags: ['typescript', 'programming', 'web-development'],
        emoji: '🚀'
      };

      const mockPost = {
        id: 'post-123',
        ...postData,
        published: false,
        createdAt: new Date(),
        updatedAt: new Date()
      };

      mockDb.createPost.mockResolvedValue(mockPost);

      const result = await blogService.createPost(postData);

      expect(result).toEqual(mockPost);
      expect(mockDb.createPost).toHaveBeenCalledWith(
        expect.objectContaining({
          title: postData.title,
          content: postData.content,
          authorId: postData.authorId,
          published: false
        })
      );
    });

    it('should validate required fields', async () => {
      const invalidPost = {
        title: '', // 🚫 Empty title
        content: 'Some content',
        authorId: 'user-123'
      };

      await expect(blogService.createPost(invalidPost))
        .rejects
        .toThrow('Title is required');
    });
  });

  describe('🔍 Post Search', () => {
    it('should find posts by tags', async () => {
      const mockPosts = [
        { id: '1', title: 'TypeScript Tips', tags: ['typescript'], emoji: '💡' },
        { id: '2', title: 'JavaScript Basics', tags: ['javascript'], emoji: '📘' }
      ];

      mockDb.findPostsByTags.mockResolvedValue([mockPosts[0]]);

      const results = await blogService.searchByTags(['typescript']);

      expect(results).toHaveLength(1);
      expect(results[0].title).toBe('TypeScript Tips');
      expect(mockDb.findPostsByTags).toHaveBeenCalledWith(['typescript']);
    });

    it('should handle empty search results', async () => {
      mockDb.findPostsByTags.mockResolvedValue([]);

      const results = await blogService.searchByTags(['nonexistent-tag']);

      expect(results).toHaveLength(0);
    });
  });
});

// 📁 src/routes/__tests__/blog.test.ts
import request from 'supertest';
import express from 'express';
import { blogRouter } from '../blog';
import { authMiddleware } from '../middleware/auth';

const app = express();
app.use(express.json());
app.use('/blog', authMiddleware, blogRouter);

describe('📝 Blog API', () => {
  const validToken = 'Bearer valid-token';
  const authorUser = { id: 'user-123', username: 'alice', role: 'author' };

  describe('POST /blog/posts', () => {
    it('should create new blog post successfully', async () => {
      const newPost = {
        title: 'Testing with Jest and Supertest 🧪',
        content: 'Learn how to test your Node.js applications effectively!',
        tags: ['testing', 'jest', 'nodejs'],
        emoji: '🧪'
      };

      const response = await request(app)
        .post('/blog/posts')
        .set('Authorization', validToken)
        .send(newPost)
        .expect(201);

      expect(response.body.success).toBe(true);
      expect(response.body.data.title).toBe(newPost.title);
      expect(response.body.data.published).toBe(false);
      expect(response.body.data.authorId).toBe(authorUser.id);
      expect(response.body.message).toContain('Post created successfully');
    });

    it('should validate post data', async () => {
      const invalidPost = {
        title: '', // 🚫 Empty title
        content: 'Some content'
      };

      const response = await request(app)
        .post('/blog/posts')
        .set('Authorization', validToken)
        .send(invalidPost)
        .expect(400);

      expect(response.body.success).toBe(false);
      expect(response.body.message).toContain('validation');
    });
  });

  describe('GET /blog/posts', () => {
    it('should return paginated blog posts', async () => {
      const response = await request(app)
        .get('/blog/posts?page=1&limit=5')
        .expect(200);

      expect(response.body.success).toBe(true);
      expect(response.body.data).toHaveProperty('posts');
      expect(response.body.data).toHaveProperty('pagination');
      expect(response.body.data.pagination).toMatchObject({
        page: 1,
        limit: 5,
        total: expect.any(Number)
      });
    });

    it('should filter posts by published status', async () => {
      const response = await request(app)
        .get('/blog/posts?published=true')
        .expect(200);

      expect(response.body.data.posts).toEqual(
        expect.arrayContaining([
          expect.objectContaining({ published: true })
        ])
      );
    });
  });

  describe('🔐 Authorization Tests', () => {
    it('should allow authors to edit their own posts', async () => {
      const updateData = {
        title: 'Updated Title 📝',
        content: 'Updated content'
      };

      const response = await request(app)
        .put('/blog/posts/user-123-post')
        .set('Authorization', validToken)
        .send(updateData)
        .expect(200);

      expect(response.body.success).toBe(true);
    });

    it('should prevent authors from editing others posts', async () => {
      const updateData = {
        title: 'Trying to hack 😈',
        content: 'This should not work'
      };

      const response = await request(app)
        .put('/blog/posts/other-user-post')
        .set('Authorization', validToken)
        .send(updateData)
        .expect(403);

      expect(response.body.success).toBe(false);
      expect(response.body.message).toContain('permission');
    });
  });
});

// 📁 src/utils/__tests__/validation.test.ts
import { validateBlogPost, sanitizeContent } from '../validation';

describe('🛡️ Validation Utils', () => {
  describe('Blog Post Validation', () => {
    it('should pass valid blog post', () => {
      const validPost = {
        title: 'Great Article 📖',
        content: 'This is amazing content!',
        tags: ['programming', 'typescript']
      };

      const result = validateBlogPost(validPost);
      expect(result.isValid).toBe(true);
      expect(result.errors).toHaveLength(0);
    });

    it('should catch multiple validation errors', () => {
      const invalidPost = {
        title: '', // 🚫 Empty
        content: 'A'.repeat(10001), // 🚫 Too long
        tags: [] // 🚫 No tags
      };

      const result = validateBlogPost(invalidPost);
      expect(result.isValid).toBe(false);
      expect(result.errors).toContain('Title is required');
      expect(result.errors).toContain('Content exceeds maximum length');
      expect(result.errors).toContain('At least one tag is required');
    });
  });

  describe('Content Sanitization', () => {
    it('should remove dangerous HTML tags', () => {
      const dangerousContent = '<script>alert("hack")</script>Safe content';
      const sanitized = sanitizeContent(dangerousContent);
      
      expect(sanitized).not.toContain('<script>');
      expect(sanitized).toContain('Safe content');
    });

    it('should preserve safe formatting', () => {
      const safeContent = '<p>Hello <strong>world</strong>!</p>';
      const sanitized = sanitizeContent(safeContent);
      
      expect(sanitized).toBe(safeContent);
    });
  });
});

🎓 Key Takeaways

You’ve learned so much about testing Node.js applications! Here’s what you can now do:

  • Set up Jest and Supertest with TypeScript configuration 💪
  • Write unit tests for services and utilities 🛡️
  • Test HTTP endpoints without starting servers 🎯
  • Mock external dependencies for isolated testing 🐛
  • Handle authentication and authorization in tests 🚀
  • Test error cases and edge conditions like a pro 🔧

Remember: Good tests give you confidence to ship code! They’re not just about finding bugs - they’re about enabling fearless development and refactoring. 🤝

🤝 Next Steps

Congratulations! 🎉 You’ve mastered testing Node.js applications with Jest and Supertest!

Here’s your next adventure:

  1. 💻 Practice writing tests for your existing Node.js projects
  2. 🏗️ Try testing more complex scenarios like file uploads and WebSockets
  3. 📚 Explore our next tutorial: “Continuous Integration with GitHub Actions”
  4. 🌟 Share your testing wins with the community!

Keep testing, keep building amazing things, and remember: every bug you catch in testing is a bug that won’t reach your users! 🚀


Happy testing! 🎉🧪✨