npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

sinon-mongo-ts

v2.1.7

Published

typescript fork of sinon-mongo

Downloads

11

Readme

sinon-mongo-ts

Forked sinon-mongo library by Daniel with added typings for sinon.mongo and smaller fixes like transaction support.

Extend sinon.js with stubs for testing code that uses the MongoDB Node.js driver

Installation

$ yarn add -D sinon-mongo-ts

sinon-mongo expects sinon >=6.3.0 and mongodb >=4.X as peer-dependencies.

If you use mongodb 3.X, please install version 1.1.0 of sinon-mongo

Usage

Simply import "sinon-mongo-ts" to extend sinon with a sinon.mongo object.

const sinon = require("sinon")
import "sinon-mongo-ts"
// sinon.mongo is now available!

Then use sinon.mongo to create stubs of various classes in the mongo API.

Important! when stubbing collections don't create stub while defining collection like:

const mockCollection = sinon.mongo.collection({
  findOne: sinon
    .stub()
    .withArgs({ name: "foo" })
    .resolves({ value: { a: "mock object" } }),
})
// every call to mockCollection.findOne will result in {value: {a: 'mock object'}} promise returned
mockCollection.findOne
  .withArgs({ name: "bar" })
  .resolves({ value: { a: "another object" } })

// Will cause
const result = await mockCollection.findOne({ name: "foo" })
console.log(result)
//  {a: 'mock object'}

const result2 = await mockCollection.findOne({ name: "bar" })
console.log(result2)
//  {a: 'mock object'}

const result3 = await mockCollection.findOne({ name: "anything" })
console.log(result3)
//  {a: 'mock object'}

It's caused by sinon, and quick solution for it for now is

// Leave collection empty or with empty stubs
const mockCollection = sinon.mongo.collection()
// Under collection definiton define queries
mockCollection.findOne
  .withArgs({ name: "foo" })
  .resolves({ value: { a: "mock object" } })
mockCollection.findOne
  .withArgs({ name: "bar" })
  .resolves({ value: { a: "another object" } })

// And then

const result = await mockCollection.findOne({ name: "foo" })
console.log(result)
//  {a: 'mock object'}

const result2 = await mockCollection.findOne({ name: "bar" })
console.log(result2)
//  {a: 'another object'}

const result3 = await mockCollection.findOne({ name: "anything" })
console.log(result3)
//  undefined

Examples / Best Typescript practices

// ---- stub collections ----
const mockCollection = sinon.mongo.collection()
// By default, every collection method is also a sinon stub
mockCollection.findOneAndUpdate
  .withArgs({ name: "foo" })
  .resolves({ value: { a: "mock object" } })

mockCollection.findOne.withArgs({ name: "foo" }).resolves({ a: "mock object" })

// ---- stub databases ----
const mockDb = sinon.mongo.db({
  customers: mockCollection,
})

// You can define if needed queries through mockDb but this ones are not supported
// by typescript
// IE
//
// mockDb.collection("customers").findOne.withArgs({name: "bar"}).resolves({a: "another object"})
//
// will work but cause typescript error, best practice is to do changes through colelction
// definition.

// ---- stub MongoClients ---
const mockMongoClient = sinon.mongo.mongoClient({
  // optionally provide a specific map of database names and stubs
  reporting: sinon.mongo.db(),
})
// By default, every MongoClient method is also a sinon stub, including the db() method
mockMongoClient.db.withArgs("myDbName").returns({ the: "mock database" })
// The connect method stub is already setup so it resolves with the mongoClient and can be chained
mockMongoClient.connect().then((mongoClient) => mongoClient.db("myDbName"))

Mock transaction (sinon-mongo-ts only)

// Also with Typescript version I added stubbing basic transactions functionality
const session = mockMongoClient.startSession()

try {
  await session.withTransaction(async () => {
    console.log("session")
  })
} catch (e) {
  console.error("error: ", e)
} finally {
  session.endSession()
}

$or query matcher (sinon-mongo-ts only)

$orMatch takes same arguments as provided to $or: [...] query, but without $or:

each object in $orMatch array must equals searched query object, it won't match partially

if couple "rows" will match, only last one will be returned

import { $orMatch } from "sinon-mongo-ts"

const mockUsers = sinon.mongo.collection()

mockUsers.findOne
  .withArgs(
    $orMatch([
      { username: "user", balance: { locked: false, amount: 100 } },
      { email: "[email protected]" },
    ])
  )
  .resolves("first")

const mockDb = sinon.mongo.db({
  users: mockUsers,
})

let query = {
  $or: [
    { username: "user4", balance: { locked: true, amount: 400 } },
    { username: "user", balance: { locked: false, amount: 100 } },
  ],
}

let result = await mockDb.collection("users").findOne(query)
console.log(result)
// "first"

API

sinon.mongo.collection

Use this API to create stubs of the MongoDB Collection type.

Every method available in the MongoDB Collection type is defaulted as a sinon stub, whose behaviour you can further customise.

sinon.mongo.collection(methodStubs[optional])

// Basic usage:
const mockCollection = sinon.mongo.collection();
mockCollection.findOne.withArgs(...).resolves(...);

// Optionally provide method stubs.
// Equivalent to the earlier example:
const mockCollection2 = sinon.mongo.collection({
  findOne: sinon.stub().withArgs(...).resolves(...);
});
// Methods that were not provided are still available as stubs
mockCollection2.findOneAndUpdate.withArgs(...).resolves(...);
sinon.assert.calledOnce(mockColletion2.insertOne);

sinon.mongo.db

Use this API to create stubs of the MongoDB Db type.

Every method available in the MongoDB Db type is defaulted as a sinon stub, whose behaviour you can further customise.

sinon.mongo.db(collectionMap[optional], methodStubs[optional])

// Basic usage:
const mockDb = sinon.mongo.db();
mockDb.collection.withArgs(...).resolves(...);
mockDb.dropCollection.withArgs(...).resolves(...);

// Optionally provide a collections map to avoid manually setting the behaviour of the collection() method
const mockDb2 = sinon.mongo.db({
  customers: mockCustomersCollection,
  organizations: mockOrganizationsCollection
});

// Optionally provide method stubs
const mockDb3 = sinon.mongo.db({}, {
  dropCollection: sinon.stub().withArgs(...).resolves(...);
});
// Method stubs that were not specifically provided are still defaulted as stubs
mockDb3.listCollections.resolves(...);

sinon.mongo.mongoClient

Use this API to create stubs of the MongoDB MongoClient type.

Every method available in the MongoDB MongoClient type is defaulted as a sinon stub, whose behaviour you can further customise.

sinon.mongo.mongoClient(databaseMap[optional], methodStubs[optional])

// Basic usage:
const mockMongoClient = sinon.mongo.mongoClient();
mockMongoClient.db.withArgs(...).resolves(...);

// Optionally provide a database map to avoid manually setting the behaviour of the db() method
const mockMongoClient2 = sinon.mongo.db({
  default: mockDefaultDatabase,
  reporting: mockReportingDatabase
});

// Optionally provide method stubs
const mockMongoClient3 = sinon.mongo.db({}, {
  isConnected: sinon.stub().withArgs(...).returns(...);
});
// Method stubs that were not specifically provided are still defaulted as stubs
mockMongoClient3.close.resolves();

sinon.mongo.documentArray

When testing code that uses some of the collection operations that return multiple documents, like find, you can use this helper API to quickly stub its toArray() result, resolving to a promise with the required array.

sinon.mongo.documentArray(documents[(optional, Array | Object)])

// code you want to test:
return collection.find({ name: "foo" }).toArray()

// in test code:
const mockCollection = sinon.mongo.collection()
mockCollection.find
  .withArgs({ name: "foo" })
  .returns(
    sinon.mongo.documentArray([
      { the: "first document" },
      { the: "second document" },
    ])
  )

// You can return an empty array or an array of a single document:
sinon.mongo.documentArray()
sinon.mongo.documentArray({ the: "single document" })

The returned documentArray stub includes stub methods for skip, limit and sort (all of them sinon stubs themselves) that you can use to test code like:

return collection
  .find({}, { email: 1, name: 1 })
  .skip(30)
  .limit(10)
  .sort({ name: 1 })
  .toArray()

sinon.mongo.documentStream

When testing code that uses some of the collection operations that return multiple documents, like find, you can use this helper API to quickly stub its stream() result, returning a readable stream that emits the provided documents.

sinon.mongo.documentStream(documents[(optional, Array | Object)])

// code you want to test (both are equivalent):
return collection.find({ name: "foo" })
return collection.find({ name: "foo" }).stream()

// in test code:
const mockCollection = sinon.mongo.collection()
mockCollection.find
  .withArgs({ name: "foo" })
  .returns(
    sinon.mongo.documentStream([
      { the: "first document" },
      { the: "second document" },
    ])
  )

// You can return an empty stream or an stream that emits a single document:
sinon.mongo.documentStream()
sinon.mongo.documentStream({ the: "single document" })

Examples

The following sections include full examples of what might be typical code using mongo and its unit tests using sinon-mongo.

Express controller

Let's say you have an express controller that talks directly to the database through an injected req.db:

const mongodb = require("mongodb")

module.exports = {
  get(req, res, next) {
    return req.db
      .collection("customers")
      .findOne({ _id: mongodb.ObjectId(req.params.id) })
      .then((cust) => res.send(cust))
      .catch(next)
  },
  post(req, res, next) {
    return req.db
      .collection("customers")
      .updateOne({ _id: mongodb.ObjectId(req.params.id) }, { $set: req.body })
      .then(() => res.sendStatus(204))
      .catch(next)
  },
}

Then a test using sinon-mongo could look like:

const mongodb = require("mongodb")
const sinon = require("sinon")
require("sinon-mongo")
const sampleController = require("../src/sample-controller")

describe("the sample controller", () => {
  let mockRequest
  let mockResponse
  let mockId
  let mockCustomerCollection
  beforeEach(() => {
    mockId = mongodb.ObjectId()
    mockRequest = {
      params: { id: mockId.toString() },
      body: { the: "mock body" },
    }
    mockResponse = {
      send: sinon.spy(),
      sendStatus: sinon.spy(),
    }

    // inject mock db and collection into the request object
    mockCustomerCollection = sinon.mongo.collection()
    mockRequest.db = sinon.mongo.db({
      customers: mockCustomerCollection,
    })
  })

  it("returns a customer by id", () => {
    const mockCustomer = { a: "mock customer" }
    mockCustomerCollection.findOne
      .withArgs({ _id: mockId })
      .resolves(mockCustomer)

    return sampleController.get(mockRequest, mockResponse).then(() => {
      sinon.assert.calledWith(mockResponse.send, mockCustomer)
    })
  })

  it("updates a customer by id", () => {
    mockCustomerCollection.updateOne
      .withArgs({ _id: mockId }, { $set: mockRequest.body })
      .resolves()

    return sampleController.post(mockRequest, mockResponse).then(() => {
      sinon.assert.calledOnce(mockCustomerCollection.updateOne)
      sinon.assert.calledWith(mockResponse.sendStatus, 204)
    })
  })
})

Classic Repository

In this example, let's assume we have a classic repository module as:

const mongodb = require("mongodb")

module.exports = (db) => ({
  findCustomersInOrganization(orgName) {
    return db.collection("customers").find({ orgName }).toArray()
  },
  updateCustomer(id, updates) {
    return db
      .collection("customers")
      .findOneAndUpdate({ _id: mongodb.ObjectId(id) }, { $set: updates })
      .then((res) => res.value)
  },
})

Notice how the db is manually injected, so in order to use this repository module you would const repo = require('./sample-repository')(dbInstance). This makes easy to inject a mock db when writing a test:

const expect = require('chai').expect;
const mongodb = require('mongodb');
const sinon = require('sinon');
require('sinon-mongo');
const sampleRepository = require('../src/sample-repository');

describe('the sample repository', () => {
  let mockId;
  let mockDb;
  let mockCustomerCollection;
  let repository;
  beforeEach(() => {
    mockId = mongodb.ObjectId();

    // inject mock db into the repository
    mockCustomerCollection = sinon.mongo.collection();
    mockDb = sinon.mongo.db({
      customers: mockCustomerCollection
    });
    repository = sampleRepository(mockDb);
  });

  it('returns all the customers for the given org name', () => {
    const mockCustomers = [{a: 'mock customer'}, {another: 'mock customer'}];
    mockCustomerCollection.find
      .withArgs({ orgName: 'mockOrgName' })
      .returns(sinon.mongo.documentArray(mockCustomers));

    return repository.findCustomersInOrganization('mockOrgName').then(customers => {
      expect(customers).to.be.eql(mockCustomers);
    });
  });

  it('updates a customer by its id', () => {
    const mockUpdates = {the: 'updated properties'};
    const mockUpdatedCustomer = {the: 'updated customer'};
    mockCustomerCollection.findOneAndUpdate
      .withArgs({ _id: sinon.match(val => mockId.equals(val) }, { $set: mockUpdates })
      .resolves({ value: mockUpdatedCustomer });

    return repository.updateCustomer(mockId, mockUpdates).then(updatedCustomer => {
      expect(updatedCustomer).to.be.eql(mockUpdatedCustomer);
    });
  });
});

A typical variant would be using a helper in the repository to retrieve the database, rather than manually injecting it. In that case you would use something like proxyquire to write your test and inject the mock db:

// in sample-repository.js
const getDb = require('./some/getdb-utility');
...
module.exports = db => ({
  findCustomersInOrganization(orgName){
    return db
      .collection('customers')
      .find({ orgName })
      .toArray();
  },
  ...
});

// In the unit test
beforeEach(() => {
  ...
  // inject mock db into the repository
  ...
  repository = proxyquire('../src/sample-repository', {
    './some/getdb-utility': () => mockDb
  });
});

License

MIT © Daniel Jimenez Garcia