Preset

mongodb latency profile

MongoDB latency depends heavily on whether a query hits an index. The preset models indexed queries at p50 (8ms) with a realistic tail for collection scans, large document reads, and write concerns propagating to secondaries.

presetp50p95p99errorRate
mongodb 8ms 100ms 250ms 0.1%
Collection scan vs index

Production MongoDB shows bimodal latency: indexed queries at 2–15ms and collection scans at 100–500ms. If your local tests run against a small dataset where even unindexed queries are fast, you'll miss missing-index bugs. slowdep adds the realistic distribution that catches these.

Quick start

Wrap collection methods

Wrap the specific collection methods you use. The native MongoDB driver exposes findOne, insertOne, updateOne, and friends as regular async functions.

import { MongoClient } from 'mongodb';
import { withLatency } from 'slowdep';

const client = new MongoClient(process.env.MONGODB_URI);
await client.connect();

const db = client.db('myapp');
const users = db.collection('users');

// Wrap specific collection methods
const slowFindOne   = withLatency(users.findOne.bind(users), 'mongodb');
const slowInsertOne = withLatency(users.insertOne.bind(users), 'mongodb');
const slowUpdateOne = withLatency(users.updateOne.bind(users), 'mongodb');

// Now all reads and writes experience realistic latency
const user = await slowFindOne({ email: 'alice@example.com' });
const { insertedId } = await slowInsertOne({ email: 'bob@example.com', createdAt: new Date() });
Wrapping a client

Wrap an entire collection with withLatencyAll

For comprehensive coverage, wrap the entire collection object. Every method — find, aggregate, bulkWrite, deleteOne — will experience realistic latency.

import { MongoClient, Collection } from 'mongodb';
import { withLatencyAll } from 'slowdep';

const client = new MongoClient(process.env.MONGODB_URI);
await client.connect();

const orders = client.db('myapp').collection<Order>('orders');

// Wrap every async method on the collection
const slowOrders = withLatencyAll(orders, 'mongodb');

// All collection methods now have realistic latency
const order = await slowOrders.findOne({ _id: orderId });
const recentOrders = await slowOrders.find({ userId, status: 'pending' }).toArray();

const stats = await slowOrders.aggregate([
  { $match: { status: 'completed' } },
  { $group: { _id: '$userId', total: { $sum: '$amount' } } }
]).toArray();
Mongoose

Use with Mongoose via the native collection

Mongoose's model methods (User.findById, etc.) are chainable query builders, not plain async functions, so they can't be wrapped directly. Instead, access the underlying native driver collection.

import mongoose from 'mongoose';
import { withLatency, withLatencyAll } from 'slowdep';

await mongoose.connect(process.env.MONGODB_URI);

// Access the native MongoDB collection through Mongoose's connection
const nativeUsers = mongoose.connection.db.collection('users');

// Wrap the native collection — works identically to the native driver
const slowUsers = withLatencyAll(nativeUsers, 'mongodb');

// Use the slow native collection for tests
const user = await slowUsers.findOne({ email: 'alice@example.com' });
const result = await slowUsers.insertOne({
  email: 'carol@example.com',
  createdAt: new Date()
});

// Alternatively, wrap specific Mongoose exec() calls
const slowExec = withLatency(
  (query: mongoose.Query<any, any>) => query.exec(),
  'mongodb'
);

const user2 = await slowExec(User.findById('507f1f77bcf86cd799439011'));
Why not wrap Mongoose models directly?

Mongoose model methods like User.findById() return a Query object, not a Promise. The Promise is only created when you call .exec() or await it. withLatency wraps the function call itself — wrapping User.findById would add latency before the Query is built, not before the DB round-trip. Wrap .exec() or use the native collection instead.

Testing

Jest tests for query patterns

import { withLatency } from 'slowdep';
import { getOrderById } from '../src/orders';

test('getOrderById times out on slow collection scan', async () => {
  // Simulate a missing index — query takes p99 time
  const slowFindOne = withLatency(
    jest.fn().mockResolvedValue({ _id: 'o1', status: 'pending' }),
    { p50: 150, p99: 600 }  // collection scan latency
  );

  await expect(
    getOrderById('o1', { findOne: slowFindOne, timeoutMs: 300 })
  ).rejects.toThrow('Query timed out');
}, 5000);
import { withLatency } from 'slowdep';

test('bulk write completes within SLA even with latency', async () => {
  const docs = Array.from({ length: 50 }, (_, i) => ({ name: `item-${i}` }));

  const slowBulkWrite = withLatency(
    jest.fn().mockResolvedValue({ insertedCount: 50, acknowledged: true }),
    'mongodb'
  );

  const start = Date.now();
  const result = await slowBulkWrite(docs);
  const elapsed = Date.now() - start;

  // Bulk write is one round-trip — latency applies once, not per document
  expect(result.insertedCount).toBe(50);
  expect(elapsed).toBeLessThan(1000);  // well within SLA
});
Production safety

Conditional wrapping by environment

import { MongoClient, Collection } from 'mongodb';
import { withLatencyAll } from 'slowdep';

function getCollection<T>(name: string): Collection<T> {
  const col = client.db('myapp').collection<T>(name);
  return process.env.NODE_ENV === 'production'
    ? col
    : withLatencyAll(col, 'mongodb');
}

export const usersCol  = getCollection<User>('users');
export const ordersCol = getCollection<Order>('orders');
What to test

Scenarios this integration helps you verify

  • Missing index detection — simulate 250ms p99 to reveal queries that would be fast on small local datasets but slow on large production collections
  • Aggregation pipeline timeout — complex $lookup and $group stages can be slow; verify your service handles them within budget
  • Write concern latency — writes with w: "majority" wait for replica acknowledgement; simulate the extra round-trip
  • Change stream lag — event-driven architectures using change streams need to account for the latency between the write and the stream event
  • Cursor exhaustion — large find().toArray() calls are expensive; realistic latency exposes code that should use pagination
  • Concurrent session handling — MongoDB multi-document transactions add latency; verify transaction timeout settings
TypeScript

Typed collection wrapper

import { MongoClient, Collection, ObjectId } from 'mongodb';
import { withLatencyAll } from 'slowdep';

interface User {
  _id?: ObjectId;
  email: string;
  name: string;
  createdAt: Date;
}

const users: Collection<User> = client.db('myapp').collection<User>('users');

// Collection<User> type preserved — full IntelliSense on slowUsers
const slowUsers: Collection<User> = withLatencyAll(users, 'mongodb');

// Return type is inferred: Promise<User | null>
const found = await slowUsers.findOne({ email: 'alice@example.com' });