UNPKG

@lykmapipo/mongoose-test-helpers

Version:
404 lines (361 loc) 10.5 kB
'use strict'; const _ = require('lodash'); const testHelpers = require('@lykmapipo/test-helpers'); const async = require('async'); const mongooseConnection = require('@lykmapipo/mongoose-connection'); const mongooseCommon = require('@lykmapipo/mongoose-common'); const mongooseFaker = require('@lykmapipo/mongoose-faker'); const sinon = require('sinon'); const mongoose = require('mongoose'); /* eslint-disable */ let MethodTypes = Object.freeze({ aggregate: 'aggregate', populate: 'populate', query: 'query', }); function getMethodType(method) { const methodType = MethodTypes[method]; return methodType || MethodTypes.query; } function chainMethod(type, object) { let mockType; switch (type) { case MethodTypes.aggregate: mockType = new mongoose.Aggregate(); break; case MethodTypes.populate: mockType = object; break; default: mockType = new mongoose.Query(); break; } return function chain(method) { let queryMock = sinon.mock(mockType); this.owner.chainedMock = queryMock; makeChainable(queryMock, object, type); makeChainableVerify(queryMock); this.returns(queryMock.object); return queryMock.expects(method); }; } function makeChainable(mock, object, mockType) { let expectsMethod = mock.expects; mock.expects = function (method) { mockType = mockType || getMethodType(method); let expectation = expectsMethod.apply(mock, arguments); expectation.owner = mock; expectation.chain = chainMethod(mockType, object).bind(expectation); return expectation; }; } function makeChainableVerify(mockResult) { let originalVerify = mockResult.verify; function chainedVerify() { originalVerify.call(mockResult); if (mockResult.chainedMock) { mockResult.chainedMock.verify(); } } mockResult.verify = chainedVerify; } let oldMock = sinon.mock; let newMock = function mock(object) { let mockResult = oldMock.apply(this, arguments); if ( object && (object instanceof mongoose.Model || object.schema instanceof mongoose.Schema) ) { makeChainable(mockResult, object); makeChainableVerify(mockResult); } return mockResult; }; sinon.mock = newMock; function sandboxMock(object) { let mockResult = oldMock.apply(null, arguments); if ( object && (object instanceof mongoose.Model || object.schema instanceof mongoose.Schema) ) { makeChainable(mockResult, object); makeChainableVerify(mockResult); } return mockResult; } // Sandbox.prototype.mock = sandboxMock; // sinon.sandbox.mock = sandboxMock; sinon.mock = sandboxMock; /** * @module mongoose-test-helpers * @description Re-usable test helpers for mongoose * @author lally elias <lallyelias87@mail.com> * @since 0.1.0 * @version 0.1.0 * @license MIT * @example * * import { setup, clear, drop } from '@lykmapipo/mongoose-test-helpers'; * before(done => { setup(done) }); * after(done => { clear(done) }); * after(done => { drop(done) }); */ process.env.NODE_ENV = 'test'; process.env.DEBUG = true; process.env.MONGODB_URI = 'mongodb://127.0.0.1/test'; /** * @function connect * @name connect * @description Opens the default mongoose connection * @param {string} [url] valid mongodb conenction string. if not provided it * will be obtained from process.env.MONGODB_URI * @param {Function} done a callback to invoke on success or failure * @author lally elias <lallyelias87@mail.com> * @since 0.1.0 * @version 0.1.0 * @example * * connect(done); * connect(<url>, done); */ const connect = (url, done) => { // ensure test database const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://127.0.0.1/test'; // normalize arguments const $url = _.isFunction(url) ? MONGODB_URI : url; const $done = _.isFunction(url) ? url : done; // establish mongoose connection mongooseConnection.connect($url, $done); }; /** * @function create * @name create * @param {...any} instances valid mongoose model instances and * optional callback at end * @description Persist given model instances * @returns {object|object[]} created model instances * @author lally elias <lallyelias87@mail.com> * @since 0.1.0 * @version 0.1.0 * @example * * create(user, done); * create(user, profile, done); * create(user, profile, done); */ const create = (...instances) => { // collect provided instances let $instances = [].concat(...instances); // obtain callback const $done = _.last( _.filter([...$instances], (instance) => { return !mongooseCommon.isInstance(instance); }) ); // collect actual model instances $instances = _.filter([...$instances], (instance) => { return mongooseCommon.isInstance(instance); }); // compact and ensure unique instances by _id $instances = _.uniqBy(_.compact([...$instances]), '_id'); // map instances to save // TODO for same model use insertMany const connected = mongooseConnection.isConnected(); let saves = _.map([...$instances], (instance) => { if (connected && instance.save) { const save = (next) => { const fn = instance.post || instance.save; fn.call(instance, (error, saved) => { next(error, saved); }); }; return save; } return undefined; }); // compact saves saves = _.compact([...saves]); // save return async.parallel(saves, $done); }; /** * @function createTestModel * @name createTestModel * @description Create a test model for testing * @param {object} [schema] model schema definition * @param {...Function} [plugins] list of plugins to apply to schema * @returns {object} valid mongoose model * @author lally elias <lallyelias87@mail.com> * @since 0.4.0 * @version 0.1.0 * @example * * const User = createTestModel(); * const User = createTestModel({ name: { type: String } }, autopopulate); */ const createTestModel = (schema, ...plugins) => { // ensure schema definition const definition = _.merge( {}, { name: { type: String, index: true, searchable: true, taggable: true, fake: (f) => f.name.findName(), }, }, schema ); // obtain options const options = _.first([...plugins], _.isPlainObject); // register dynamic model const testModel = mongooseConnection.createModel( definition, { timestamps: true, ...options }, ..._.filter([...plugins, mongooseFaker], _.isFunction) ); // return created model return testModel; }; /** * @function mockModel * @name mockModel * @description Mock existing mongoose model * @param {object} model valid mongoose model * @returns {object} valid mock of provided mongoose model * @author lally elias <lallyelias87@mail.com> * @since 0.5.0 * @version 0.1.0 * @example * * const Mock = mockModel(User); * const find = Mock.expects('find').yields(null, [{...}]); * * User.find((error, results) => { * Mock.verify(); * Mock.restore(); * expect(find).to.have.been.calledOnce; * expect(error).to.not.exist; * expect(results).to.exist; * expect(results).to.have.have.length(1); * done(error, results); * }); */ const mockModel = (model) => { const mocked = mongooseConnection.isModel(model) ? testHelpers.sinon.mock(model) : undefined; return mocked; }; /** * @function mockInstance * @name mockInstance * @description Mock provided model instance * @param {object} instance valid model instance * @returns {object} valid mock of provided model instance * @author lally elias <lallyelias87@mail.com> * @since 0.5.0 * @version 0.1.0 * @example * * const mock = mockInstance(new User()); * const save = mock.expects('save').yields(null, {...}); * * user.save((error, results) => { * Mock.verify(); * Mock.restore(); * expect(save).to.have.been.calledOnce; * expect(error).to.not.exist; * expect(result).to.exist; * done(error, results); * }); */ const mockInstance = (instance) => { const mocked = mongooseCommon.isInstance(instance) ? testHelpers.sinon.mock(instance) : undefined; return mocked; }; /** * @function getModel * @name getModel * @description Try obtain already registered * @author lally elias <lallyelias87@mail.com> * @since 0.1.0 * @version 0.1.0 * @example * * const User = getModel('User'); * const User = model('User'); */ const model = mongooseConnection.model; Object.defineProperty(exports, 'chai', { enumerable: true, get: function () { return testHelpers.chai; } }); Object.defineProperty(exports, 'expect', { enumerable: true, get: function () { return testHelpers.expect; } }); Object.defineProperty(exports, 'fake', { enumerable: true, get: function () { return testHelpers.fake; } }); Object.defineProperty(exports, 'faker', { enumerable: true, get: function () { return testHelpers.faker; } }); Object.defineProperty(exports, 'mock', { enumerable: true, get: function () { return testHelpers.mock; } }); Object.defineProperty(exports, 'restore', { enumerable: true, get: function () { return testHelpers.restore; } }); Object.defineProperty(exports, 'should', { enumerable: true, get: function () { return testHelpers.should; } }); Object.defineProperty(exports, 'sinon', { enumerable: true, get: function () { return testHelpers.sinon; } }); Object.defineProperty(exports, 'spy', { enumerable: true, get: function () { return testHelpers.spy; } }); Object.defineProperty(exports, 'stub', { enumerable: true, get: function () { return testHelpers.stub; } }); Object.defineProperty(exports, 'clear', { enumerable: true, get: function () { return mongooseConnection.clear; } }); Object.defineProperty(exports, 'disableDebug', { enumerable: true, get: function () { return mongooseConnection.disableDebug; } }); Object.defineProperty(exports, 'disconnect', { enumerable: true, get: function () { return mongooseConnection.disconnect; } }); Object.defineProperty(exports, 'drop', { enumerable: true, get: function () { return mongooseConnection.drop; } }); Object.defineProperty(exports, 'enableDebug', { enumerable: true, get: function () { return mongooseConnection.enableDebug; } }); Object.defineProperty(exports, 'getModel', { enumerable: true, get: function () { return mongooseConnection.model; } }); exports.connect = connect; exports.create = create; exports.createTestModel = createTestModel; exports.mockInstance = mockInstance; exports.mockModel = mockModel; exports.model = model;