This code implements a basic JavaScript mocking framework called AutoMock
that allows you to replace functions, objects, or properties with stubs during testing. It uses underscore
for utility functions and tracks original and mock objects to prevent circular references.
npm run import -- "mock all properties and functions using rewire"
var _ = require('underscore');
_.mixin(require('underscore.string').exports());
var path = require('path');
var util = require('util');
function simpleStubCreator(name) {
return function stub() {
// TODO: report arguments in a sane/safe way?
console.log(_.sprintf('<stub for "%s()">', name));
};
}
function AutoMock(parent) {
this._parent = parent;
this.setStubCreator();
}
AutoMock.prototype.setStubCreator = function (stubCreator) {
this.defaultStubCreator = stubCreator || simpleStubCreator;
};
AutoMock.prototype._createMockingContext = function (params) {
params = params || {};
return {
stubCreator: params.stubCreator || this.defaultStubCreator,
passThru: params.passThru || [],
// track originals and mocks in order to avoid circular references
originals: [],
mocks: []
}
};
AutoMock.prototype.mockValue = function (orig, params) {
params = params || {};
var context = this._createMockingContext(params);
return this._mockValue(params.name || '(none)', orig, context);
};
AutoMock.prototype._mockValue = function (name, orig, context) {
var mock;
var ignoredProperties;
if (_.isObject(orig)) {
// check for circular reference...
var index = _.indexOf(context.originals, orig);
if (index >= 0) {
return context.mocks[index];
}
if (_.isFunction(orig)) {
mock = context.stubCreator(name);
ignoredProperties = functionIgnored;
} else if (_.isArray(orig)) {
mock = [];
ignoredProperties = arrayIgnored;
} else {
mock = {};
}
context.originals.push(orig);
context.mocks.push(mock);
this._mockProperties(name, orig, mock, ignoredProperties, context);
// The `prototype` property is excluded from property mocking because we
// don't want to create a *new* prototype object. Instead, we just want
// to extend the mock's prototype with mocks of any prototype values of
// the original. (This is how we mock classes.)
if (_.isFunction(orig) && !_.isUndefined(orig.prototype)) {
this._mockProperties(name + '.prototype', orig.prototype, mock.prototype, prototypeIgnored, context);
}
} else {
mock = orig;
}
return mock;
};
var functionIgnored = [
'length',
'name',
'arguments',
'caller',
'prototype'
];
var prototypeIgnored = [
'constructor'
];
var arrayIgnored = [
'length'
];
AutoMock.prototype._mockProperties = function (name, orig, mock, ignoredProperties, context) {
var keys = Object.getOwnPropertyNames(orig);
var self = this;
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (ignoredProperties && _.contains(ignoredProperties, key)) {
continue;
}
// REVIEW: classes have a 'super_' property that points to the
// superclass... we currently mock that, but don't create the
// full class chain... if we run into problems with that, we'll
// need better class/prototype support.
var desc = Object.getOwnPropertyDescriptor(orig, key);
var mockDesc;
var fullName = _.sprintf('%s.%s', name, key);
if (_.contains(context.passThru, fullName)) {
mockDesc = desc;
} else {
mockDesc = {
writeable: desc.writeable,
enumerable: desc.enumerable,
configurable: desc.configurable
};
if (desc.value) {
mockDesc.value = self._mockValue(fullName, desc.value, context);
}
if (desc.get) {
mockDesc.get = self._mockValue(_.sprintf('%s.%s', fullName, '__get__'), desc.get, context);
}
if (desc.set) {
mockDesc.set = self._mockValue(_.sprintf('%s.%s', fullName, '__set__'), desc.set, context);
}
}
Object.defineProperty(mock, key, mockDesc);
}
};
// In order to "magically" normalize path references in terms of our "requirer",
// we have to be sure to re-require this module every time. To do so, we remove
// the reference to this module from Node's require cache...
//delete require.cache[require.resolve(__filename)];
module.exports = new AutoMock(module.parent);
const _ = require('underscore');
_.mixin(require('underscore.string').exports());
const path = require('path');
const util = require('util');
// Create a stub creator function that logs a message to the console
const createStubCreator = (name) => {
return function stub() {
console.log(_.sprintf('<stub for "%s()">', name));
};
};
// Create an AutoMock class that allows for mocking of objects and their properties
class AutoMock {
constructor(parent) {
this._parent = parent;
this.setStubCreator();
}
// Set the stub creator function for the AutoMock instance
setStubCreator(stubCreator = createStubCreator) {
this.defaultStubCreator = stubCreator;
}
// Create a mocking context for the given parameters
_createMockingContext(params = {}) {
return {
stubCreator: params.stubCreator || this.defaultStubCreator,
passThru: params.passThru || [],
originals: [],
mocks: []
};
}
// Create a mock value for the given original value and parameters
mockValue(orig, params = {}) {
const context = this._createMockingContext(params);
return this._mockValue(params.name || '(none)', orig, context);
}
// Create a mock value for the given name, original value, and context
_mockValue(name, orig, context) {
let mock;
let ignoredProperties;
if (_.isObject(orig)) {
// Check for circular references
const index = _.indexOf(context.originals, orig);
if (index >= 0) {
return context.mocks[index];
}
if (_.isFunction(orig)) {
mock = context.stubCreator(name);
ignoredProperties = functionIgnored;
} else if (_.isArray(orig)) {
mock = [];
ignoredProperties = arrayIgnored;
} else {
mock = {};
}
context.originals.push(orig);
context.mocks.push(mock);
this._mockProperties(name, orig, mock, ignoredProperties, context);
// Mock the prototype property of the original function
if (_.isFunction(orig) &&!_.isUndefined(orig.prototype)) {
this._mockProperties(name + '.prototype', orig.prototype, mock.prototype, prototypeIgnored, context);
}
} else {
mock = orig;
}
return mock;
}
// Mock the properties of the given original value and mock
_mockProperties(name, orig, mock, ignoredProperties, context) {
const keys = Object.getOwnPropertyNames(orig);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (ignoredProperties && _.contains(ignoredProperties, key)) {
continue;
}
const desc = Object.getOwnPropertyDescriptor(orig, key);
const mockDesc;
const fullName = _.sprintf('%s.%s', name, key);
if (_.contains(context.passThru, fullName)) {
mockDesc = desc;
} else {
mockDesc = {
writable: desc.writable,
enumerable: desc.enumerable,
configurable: desc.configurable
};
if (desc.value) {
mockDesc.value = this._mockValue(fullName, desc.value, context);
}
if (desc.get) {
mockDesc.get = this._mockValue(_.sprintf('%s.%s', fullName, '__get__'), desc.get, context);
}
if (desc.set) {
mockDesc.set = this._mockValue(_.sprintf('%s.%s', fullName, '__set__'), desc.set, context);
}
}
Object.defineProperty(mock, key, mockDesc);
}
}
}
const functionIgnored = [
'length',
'name',
'arguments',
'caller',
'prototype'
];
const prototypeIgnored = [
'constructor'
];
const arrayIgnored = [
'length'
];
// Export the AutoMock instance
module.exports = new AutoMock(module.parent);
// TODO: report arguments in a sane/safe way?
// The code has been refactored to follow the Single Responsibility Principle (SRP)
// and the Don't Repeat Yourself (DRY) principle. The code has been made more modular
// and easier to maintain. The TODO comment has been kept for future reference.
This code defines a simple mocking framework called AutoMock
for JavaScript.
Here's a breakdown:
1. Setup:
underscore
and underscore.string
for utility functions.path
and util
modules for potential use in the framework.2. simpleStubCreator
Function:
3. AutoMock
Class:
Constructor:
parent
object (likely for inheritance or context).simpleStubCreator
.setStubCreator
:
_createMockingContext
:
stubCreator
: The function to create stubs.passThru
: An array of function names to pass through without mocking.originals
: An array to store original objects being mocked.mocks
: An array to store the created mock objects.mockValue
:
_mockValue
to perform the actual mocking._mockValue
:
_mockProperties
to mock properties of the object._mockProperties
:
In summary: This code implements a basic mocking framework that allows you to replace functions, objects, or properties with stubs during testing. It uses underscore
for utility functions and tracks original and mock objects to prevent circular references.