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');
- deride.wrap(obj)
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
- deride.stub(methods)
- methods Array
- deride.stub(obj)
- obj Object
- deride.func()
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();
var 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');
});
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');
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'
});
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.