@lykmapipo/mongoose-test-helpers
Version:
mongoose test helpers
404 lines (361 loc) • 10.5 kB
JavaScript
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;
;