Research
Security News
Kill Switch Hidden in npm Packages Typosquatting Chalk and Chokidar
Socket researchers found several malicious npm packages typosquatting Chalk and Chokidar, targeting Node.js developers with kill switches and data theft.
Mocking library based on composition
The inspiration for this was that my colleague was having a look at other mocking frameworks and mentioned to me that they do not work when using Object.freeze
in the objects to enforce encapsulation. This library builds on composition to create a mocking library that can work with objects which are frozen.
Install the module with: npm install deride
var deride = require('deride');
CAUTION Remember when you use this function about the good practice recommended in the book Growing Object-Oriented Software, Guided by Tests Chapter 8: Only Mock Types That You Own
obj
.expect.method
.called.times(n)obj
.expect.method
.called.once()obj
.expect.method
.called.twice()obj
.expect.method
.called.lt()obj
.expect.method
.called.lte()obj
.expect.method
.called.gt()obj
.expect.method
.called.gte()obj
.expect.method
.called.never()obj
.expect.method
.called.withArg(arg)obj
.expect.method
.called.withArgs(args)obj
.expect.method
.called.withMatch(pattern)obj
.expect.method
.called.matchExactly(args)All of the above can be negated e.g. negating the .withArgs
would be:
obj
.expect.method
.called.not
.withArgs(args)obj
.expect.method
.called.reset()obj
.called.reset()obj
.setup.method
.toDoThis(func)obj
.setup.method
.toReturn(value)obj
.setup.method
.toResolveWith(value)obj
.setup.method
.toRejectWith(value)obj
.setup.method
.toThrow(message)obj
.setup.method
.toEmit(event, args)obj
.setup.method
.toCallbackWith(args)obj
.setup.method
.toTimeWarp(milliseconds)obj
.setup.method
.when(args|function).[toDoThis|toReturn|toRejectWith|toResolveWith|toThrow|toEmit|toCallbackWith|toTimeWarp]obj
.setup.method
.toIntercept(func)var Person = function(name) {
return Object.freeze({
greet: function(otherPersonName) {
console.log(name, 'says hello to', otherPersonName);
},
echo: function(name) {
return name;
}
});
}
Stubbing an object simply creates an anonymous object, with all the method specified and then the object is wrapped to provide all the expectation functionality of the library
var bob = deride.stub(['greet']);
bob.greet('alice');
bob.expect.greet.called.times(1);
To stub an object with pre set properties call the stub method with a properties array in the second parameter. We are following the defineProperty definition as can be found in the below link.
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
var bob = deride.stub(['greet'], [{name: 'age', options: { value: 25, enumerable: true}}]);
bob.age === 25;
var Person = {
greet: function(name) {
return 'alice sas hello to ' + name;
},
};
var bob = deride.stub(Person);
bob.greet('alice');
bob.expect.greet.called.once();
var func = deride.func();
func.setup.toReturn(1);
var value = func(1, 2, 3);
assert.equal(value, 1);
var f = function (name) { return 'hello ' + name; };
var func = deride.func(f);
assert(func('bob'), 'hello bob');
func.expect.called.withArg('bob');
var f = function (name) { return 'hello ' + name; };
var func = deride.func(when.lift(f));
func('bob').then(function (result) {
assert(result, 'hello bob');
func.expect.called.withArg('bob');
}).finally(done);
var bob = deride.stub([]);
bob.on('message', function() {
done();
});
bob.emit('message', 'payload');
bob.setup.greet.toEmit('testing');
bob.on('testing', function() {
done();
});
bob.greet('bob');
bob.setup.greet.toEmit('testing', 'arg1', { a: 1 });
bob.on('testing', function(a1, a2) {
a1.should.eql('arg1');
a2.should.eql({ a: 1 });
done();
});
bob.greet('bob');
var bob = new Person('bob');
bob = deride.wrap(bob);
bob.greet('alice');
bob.expect.greet.called.times(1);
var bob = new Person('bob');
bob = deride.wrap(bob);
bob.greet('alice');
bob.expect.greet.called.once();
bob.greet('sally');
bob.expect.greet.called.twice();
lt
, lte
, gt
and gte
methodsvar bob = new Person('bob');
bob = deride.wrap(bob);
bob.greet('alice');
bob.greet('alice');
bob.greet('alice');
bob.expect.greet.called.lt(4);
bob.expect.greet.called.lte(3);
bob.expect.greet.called.gt(2);
bob.expect.greet.called.gte(3);
var bob = new Person('bob');
bob = deride.wrap(bob);
bob.expect.greet.called.never();
var bob = new Person('bob');
bob = deride.wrap(bob);
bob.greet('alice');
bob.echo('alice');
bob.expect.greet.called.once();
bob.expect.echo.called.once();
bob.called.reset();
bob.expect.greet.called.never();
bob.expect.echo.called.never();
var bob = new Person('bob');
bob = deride.wrap(bob);
bob.greet('alice');
bob.greet('bob');
bob.expect.greet.called.withArgs('bob');
var bob = new Person('bob');
bob = deride.wrap(bob);
bob.greet('alice', ['james'], 987);
bob.expect.greet.called.matchExactly('alice', ['james'], 987);
var bob = new Person('bob');
bob = deride.wrap(bob);
bob.setup.greet.toDoThis(function(otherPersonName) {
return util.format('yo %s', otherPersonName);
});
var result = bob.greet('alice');
result.should.eql('yo alice');
var bob = new Person('bob');
bob = deride.wrap(bob);
bob.setup.greet.toReturn('foobar');
var result = bob.greet('alice');
result.should.eql('foobar');
var bob = new Person('bob');
bob = deride.wrap(bob);
bob.setup.greet.toResolveWith('foobar');
bob.greet('alice').then(function(result) {
result.should.eql('foobar');
});
var bob = new Person('bob');
bob = deride.wrap(bob);
bob.setup.greet.toRejectWith('foobar');
bob.greet('alice').catch(function(result) {
result.should.eql('foobar');
});
var bob = new Person('bob');
bob = deride.wrap(bob);
bob.setup.greet.toThrow('BANG');
should(function() {
bob.greet('alice');
}).
throw(/BANG/);
var bob = new Person('bob');
bob = deride.wrap(bob);
bob.setup.chuckle.toCallbackWith(0, 'boom');
bob.chuckle(function(err, message) {
assert.equal(err, 0);
assert.equal(message, 'boom');
});
function
var bob = new Person('bob');
bob = deride.wrap(bob);
bob.setup.chuckle.toCallbackWith(0, 'boom');
bob.chuckle('bob', function() {
done('this was not the callback');
}, function(err, message) {
assert.equal(err, 0);
assert.equal(message, 'boom');
done();
});
var Person = function(name) {
return Object.freeze({
foobar: function(timeout, callback) {
setTimeout(function() {
callback('result');
}, timeout);
}
});
};
var timeout = 10000;
var bob = new Person('bob');
bob = deride.wrap(bob);
bob.setup.foobar.toTimeWarp(timeout);
bob.foobar(timeout, function(message) {
assert.equal(message, 'result');
});
Currently this will allow you to inspect the arguments that are passed to a method, but it will not pass any modifications to the real method.
var bob = new Person('bob');
bob = deride.wrap(bob);
bob.setup.greet.toIntercept(function () {
console.log(arguments); // { '0': 'sally', '1': { message: 'hello %s'} }
});
bob.greet('sally', {message: 'hello %s'});
var bob = new Person('bob');
bob = deride.wrap(bob);
bob.setup.greet.when('alice').toReturn('foobar');
bob.setup.greet.toReturn('barfoo');
var result1 = bob.greet('alice');
var result2 = bob.greet('bob');
result1.should.eql('foobar');
result2.should.eql('barfoo');
var bob = new Person('bob');
bob = deride.wrap(bob);
bob.setup.greet.when('alice').toDoThis(function(otherPersonName) {
return util.format('yo yo %s', otherPersonName);
});
bob.setup.greet.toDoThis(function(otherPersonName) {
return util.format('yo %s', otherPersonName);
});
var result1 = bob.greet('alice');
var result2 = bob.greet('bob');
result1.should.eql('yo yo alice');
result2.should.eql('yo bob');
var bob = new Person('bob');
bob = deride.wrap(bob);
bob.setup.greet.when('alice').toThrow('BANG');
should(function() {
bob.greet('alice');
}).
throw (/BANG/);
should(function() {
bob.greet('bob');
}).not.
throw (/BANG/);
var bob = new Person('bob');
bob = deride.wrap(bob);
bob.setup.chuckle.toCallbackWith([0, 'boom']);
bob.setup.chuckle.when('alice').toCallbackWith([0, 'bam']);
bob.chuckle(function(err, message) {
assert.equal(err, 0);
assert.equal(message, 'boom');
bob.chuckle('alice', function(err, message) {
assert.equal(err, 0);
assert.equal(message, 'bam');
});
});
var Person = function(name) {
return Object.freeze({
foobar: function(timeout, callback) {
setTimeout(function() {
callback('result');
}, timeout);
}
});
};
var timeout1 = 10000;
var timeout2 = 20000;
var bob = new Person('bob');
bob = deride.wrap(bob);
bob.setup.foobar.toTimeWarp(timeout1);
bob.setup.foobar.when(timeout2).toTimeWarp(timeout2);
bob.foobar(timeout1, function(message) {
assert.equal(message, 'result');
bob.foobar(timeout2, function(message) {
assert.equal(message, 'result');
done();
});
});
If a function is passed to the when
, then this will be invoked with the arguments passed. The function that has been setup will be called if this predicate returns truthy.
function resourceMatchingPredicate(msg) {
var content = JSON.parse(msg.content.toString());
return content.resource === 'talula';
}
bob.setup.chuckle.toReturn('chuckling');
bob.setup.chuckle.when(resourceMatchingPredicate).toReturn('chuckle talula');
var matchingMsg = {
//...
//other properties that we do not know until runtime
//...
content: new Buffer(JSON.stringify({
resource: 'talula'
}))
};
bob.chuckle(matchingMsg).should.eql('chuckle talula');
function tatulaMatchingPredicate(msg) {
var content = JSON.parse(msg.content.toString());
return content.resource === 'talula';
}
function babulaMatchingPredicate(msg) {
var content = JSON.parse(msg.content.toString());
return content.resource === 'babula';
}
bob.setup.chuckle.toReturn('chuckling');
bob.setup.chuckle.when(tatulaMatchingPredicate).toReturn('chuckle talula');
bob.setup.chuckle.when(babulaMatchingPredicate).toReturn('chuckle babula');
var matchingMsg = {
//...
//other properties that we do not know until runtime
//...
content: new Buffer(JSON.stringify({
resource: 'talula'
}))
};
bob.chuckle(matchingMsg).should.eql('chuckle talula');
matchingMsg.content.resource = 'babula';
bob.chuckle(matchingMsg).should.eql('chuckle babula');
### Setup for multiple invocations
bob.setup.greet
.toReturn('alice')
.twice()
.and.then
.toReturn('sally');
bob.greet().should.eql('alice');
bob.greet().should.eql('alice');
bob.greet().should.eql('sally');
bob.greet().should.eql('sally');
var normalResult = bob.greet('talula');
var normalSimon = bob.greet('simon');
bob.setup.greet
.when('simon')
.toReturn('alice')
.twice();
// default Person behaviour
should(bob.greet('talula')).eql(normalResult);
// overridden behaviour
bob.greet('simon').should.eql('alice');
bob.greet('simon').should.eql('alice');
// default Person behaviour
should(bob.greet('simon')).eql(normalSimon);
var normalResult = bob.greet('alice');
debug('normalResult', normalResult);
bob.setup.greet
.toReturn('alice')
.twice()
.and.then
.fallback();
bob.greet('alice').should.eql('alice');
bob.greet('alice').should.eql('alice');
should(bob.greet('alice')).eql(normalResult);
var bob = deride.wrap(bob);
bob.greet('jack', 'alice');
bob.greet('bob');
bob.expect.greet.invocation(0).withArg('alice');
bob.expect.greet.invocation(1).withArg('bob');
var bob = deride.wrap(bob);
bob.greet('alice', {
name: 'bob',
a: 1
}, 'sam');
bob.expect.greet.called.withArg('sam');
var bob = deride.wrap(bob);
bob.greet('alice', {
name: 'bob',
a: 1
});
bob.expect.greet.called.withArg({
name: 'bob'
});
## Use a RexExp match for the assertion on any args being used in any invocation
var bob = deride.stub(['greet']);
bob.greet('The inspiration for this was that my colleague was having a');
bob.greet({a: 123, b: 'talula'}, 123, 'something');
bob.expect.greet.called.withMatch(/^The inspiration for this was/);
var bob = deride.stub(['greet']);
bob.greet('The inspiration for this was that my colleague was having a');
bob.greet({a: 123, b: { a: {'talula'}}, 123, 'something');
bob.expect.greet.called.withMatch(/^talula/gi);
Please ensure that you run grunt
, have no style warnings and that all the tests are passing.
Copyright (c) 2014 Andrew Rea
Copyright (c) 2014 James Allen
Licensed under the MIT license.
FAQs
Mocking library based on composition
We found that deride demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket researchers found several malicious npm packages typosquatting Chalk and Chokidar, targeting Node.js developers with kill switches and data theft.
Security News
pnpm 10 blocks lifecycle scripts by default to improve security, addressing supply chain attack risks but sparking debate over compatibility and workflow changes.
Product
Socket now supports uv.lock files to ensure consistent, secure dependency resolution for Python projects and enhance supply chain security.