Skip to content

Mean 19 Testing

DodaTech 6 min read

title: "Testing the MEAN Stack — Unit and Integration Tests" description: "Learn testing strategies for the MEAN Stack including Express API tests with Supertest, Angular component tests with Jasmine, and end-to-end testing." weight: 29 date: 2026-06-28 lastmod: 2026-06-28 tags: [frameworks, mean]

Testing the MEAN stack requires testing each layer: Express API endpoints, Mongoose models, Angular components and services, and end-to-end user flows.

What You'll Learn

You will write unit tests for Express routes and Mongoose models, integration tests for API endpoints with Supertest, and Angular component tests with Jasmine.

Why It Matters

Testing catches bugs before they reach production, documents expected behavior, and enables confident refactoring.

Real-World Use

DodaZIP has 80 percent test coverage with Supertest for API endpoints, Jasmine for Angular components, and Cypress for end-to-end tests.

flowchart LR
    A[Test Pyramid] --> B[Unit Tests]
    A --> C[Integration Tests]
    A --> D[E2E Tests]
    B --> E[Models, Services]
    C --> F[API Endpoints]
    D --> G[User Flows]
    style A fill:#4a90d9,color:#fff

Testing Express with Supertest

Install and configure testing tools for the backend.

cd backend
npm install --save-dev jest supertest mongodb-memory-server
// backend/__tests__/setup.js
const { MongoMemoryServer } = require('mongodb-memory-server');
const mongoose = require('mongoose');

let mongoServer;

beforeAll(async () => {
  mongoServer = await MongoMemoryServer.create();
  await mongoose.connect(mongoServer.getUri());
});

afterAll(async () => {
  await mongoose.disconnect();
  await mongoServer.stop();
});

afterEach(async () => {
  const collections = mongoose.connection.collections;
  for (const key in collections) {
    await collections[key].deleteMany({});
  }
});

Expected output: In-memory MongoDB for testing. Each test suite uses a fresh database that is cleaned between tests.

Testing API Endpoints

Write integration tests for Express routes.

// backend/__tests__/product.routes.test.js
const request = require('supertest');
const express = require('express');
const productRoutes = require('../routes/productRoutes');
const Product = require('../models/Product');

const app = express();
app.use(express.json());
app.use('/api/products', productRoutes);

describe('Product API', () => {
  beforeEach(async () => {
    await Product.create([
      { name: 'Product 1', price: 10, category: 'Electronics' },
      { name: 'Product 2', price: 20, category: 'Books' },
    ]);
  });

  describe('GET /api/products', () => {
    it('should return all products', async () => {
      const res = await request(app).get('/api/products');
      expect(res.status).toBe(200);
      expect(res.body.success).toBe(true);
      expect(res.body.data).toHaveLength(2);
    });

    it('should paginate products', async () => {
      const res = await request(app).get('/api/products?page=1&limit=1');
      expect(res.status).toBe(200);
      expect(res.body.data).toHaveLength(1);
      expect(res.body.pagination.total).toBe(2);
    });

    it('should filter by category', async () => {
      const res = await request(app).get('/api/products?category=Books');
      expect(res.status).toBe(200);
      expect(res.body.data).toHaveLength(1);
      expect(res.body.data[0].category).toBe('Books');
    });
  });

  describe('POST /api/products', () => {
    it('should create a product', async () => {
      const res = await request(app)
        .post('/api/products')
        .send({ name: 'New Product', price: 30, category: 'Electronics' });
      expect(res.status).toBe(201);
      expect(res.body.success).toBe(true);
      expect(res.body.data.name).toBe('New Product');
    });

    it('should return 400 for invalid data', async () => {
      const res = await request(app)
        .post('/api/products')
        .send({ name: 'A' }); // Missing price and category
      expect(res.status).toBe(400);
    });
  });
});

Expected output: Tests verify list, pagination, filtering, creation, and validation. The in-memory database is seeded with test data and cleaned between runs.

Testing Mongoose Models

Test model validation and custom methods.

// backend/__tests__/user.model.test.js
const mongoose = require('mongoose');
const User = require('../models/User');

describe('User Model', () => {
  it('should create a valid user', async () => {
    const user = await User.create({
      name: 'Test User',
      email: 'test@example.com',
      password: 'hashedpassword123'
    });
    expect(user.name).toBe('Test User');
    expect(user.email).toBe('test@example.com');
    expect(user.role).toBe('user');
  });

  it('should require name and email', async () => {
    await expect(User.create({})).rejects.toThrow();
  });

  it('should enforce unique email', async () => {
    await User.create({ name: 'User 1', email: 'same@test.com', password: 'hash' });
    await expect(
      User.create({ name: 'User 2', email: 'same@test.com', password: 'hash' })
    ).rejects.toThrow();
  });

  it('should have timestamps', async () => {
    const user = await User.create({
      name: 'Timed User',
      email: 'time@test.com',
      password: 'hash'
    });
    expect(user.createdAt).toBeDefined();
    expect(user.updatedAt).toBeDefined();
  });
});

Expected output: Model tests verify creation, validation, uniqueness constraints, and automatic timestamps.

Testing Angular Components

Write component tests with Jasmine.

// frontend/src/app/components/product-list.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { of, throwError } from 'rxjs';
import { ProductListComponent } from './product-list.component';
import { ProductService } from '../../services/product.service';

describe('ProductListComponent', () => {
  let component: ProductListComponent;
  let fixture: ComponentFixture<ProductListComponent>;
  let productService: jasmine.SpyObj<ProductService>;

  const mockProducts = [
    { _id: '1', name: 'Product 1', price: 10, category: 'Electronics', inStock: true,
      description: 'Desc 1', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }
  ];

  beforeEach(async () => {
    const spy = jasmine.createSpyObj('ProductService', ['getProducts']);
    await TestBed.configureTestingModule({
      imports: [ProductListComponent, HttpClientTestingModule],
      providers: [{ provide: ProductService, useValue: spy }]
    }).compileComponents();

    productService = TestBed.inject(ProductService) as jasmine.SpyObj<ProductService>;
    fixture = TestBed.createComponent(ProductListComponent);
    component = fixture.componentInstance;
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should load products on init', () => {
    productService.getProducts.and.returnValue(of({
      success: true, data: mockProducts,
      pagination: { page: 1, limit: 10, total: 1, pages: 1 }
    }));
    fixture.detectChanges();
    expect(component.products.length).toBe(1);
    expect(component.loading).toBe(false);
  });

  it('should handle errors', () => {
    productService.getProducts.and.returnValue(throwError(() => ({ message: 'Error' })));
    fixture.detectChanges();
    expect(component.error).toBeTruthy();
    expect(component.loading).toBe(false);
  });
});

Expected output: Component tests verify initialization, data loading, error handling, and template rendering.

Common Mistakes

  1. Not using an in-memory database for tests: Tests that hit a real database are slow and depend on external services. Use mongodb-memory-server.

  2. Testing implementation details instead of behavior: Test what the code does, not how it does it. Focus on inputs and outputs.

  3. Not cleaning test data between tests: Shared state between tests causes flaky tests. Clean the database after each test.

  4. Writing too many end-to-end tests: E2E tests are slow and brittle. Focus on unit and integration tests. Use a few E2E tests for critical user flows.

  5. Not mocking external services: Tests that call real APIs are slow and unreliable. Mock HTTP requests and external services.

Practice Questions

  1. What is the purpose of mongodb-memory-server?

It provides an in-memory MongoDB instance for testing, eliminating the need for a real database connection.

  1. How do you test Express route handlers?

Use Supertest to make HTTP requests to the Express app and assert on the response status and body.

  1. How do you test Angular components that call services?

Mock the service with jasmine.createSpyObj and use HttpClientTestingModule for HTTP calls.

  1. What should you test in Mongoose model tests?

Validation rules, required fields, unique constraints, default values, timestamps, and custom methods.

  1. What is the test pyramid?

A concept that suggests having many unit tests, fewer integration tests, and even fewer end-to-end tests.

Challenge

Write a complete test suite for a MEAN product API with: model tests (validation, timestamps), API route tests (CRUD, pagination, filtering, error handling), and Angular component tests (loading, success, error states).

Frequently Asked Questions

Should I test the frontend and backend separately?

Yes. Test each layer in isolation. Integration tests verify that they work together. E2E tests verify the complete system.

How do I test authentication endpoints?

Create a test user in the setup, generate a valid JWT token, and include it in the Authorization header of test requests.

What is code coverage and how much do I need?

Code coverage measures what percentage of code is executed during tests. Aim for 70-80 percent coverage. Focus on critical paths.

{{< faq "How do I run tests in CI/CD?" >} Configure GitHub Actions or Railway CI to run tests on every push. Fail the build if tests fail. {{< /faq >}}

Should I test the database connection?

Test that your models work correctly with an in-memory database. Do not test the MongoDB connection itself in unit tests.

Mini Project

Create a complete test suite for a user management API: model tests (User validation, password hashing), API tests (register, login, profile, error cases), and Angular tests (login component, user list component).

What's Next

Build a MEAN Project applying all concepts.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro