deride
v1.3.0
Published
Mocking library based on composition
Downloads
367
Readme
deride
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.
Getting Started
Install the module with: npm install deride
var deride = require('deride');
Documentation
Mocking
- 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()
Expectations
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)
Resetting the counts / called with args
obj
.expect.method
.called.reset()obj
.called.reset()
Setup
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)- setup for multiple invocations
Examples
The context
var Person = function(name) {
return Object.freeze({
greet: function(otherPersonName) {
console.log(name, 'says hello to', otherPersonName);
},
echo: function(name) {
return name;
}
});
}
Creating a stubbed object
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);
Creating a stubbed object with properties
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;
Creating a stubbed object based on an existing object
var Person = {
greet: function(name) {
return 'alice sas hello to ' + name;
},
};
var bob = deride.stub(Person);
bob.greet('alice');
bob.expect.greet.called.once();
Creating a single mocked method
var func = deride.func();
func.setup.toReturn(1);
var value = func(1, 2, 3);
assert.equal(value, 1);
Wrapping an existing function
var f = function (name) { return 'hello ' + name; };
var func = deride.func(f);
assert(func('bob'), 'hello bob');
func.expect.called.withArg('bob');
Wrapping an existing promise function
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);
Events
Force the emit of an event on an object
var bob = deride.stub([]);
bob.on('message', function() {
done();
});
bob.emit('message', 'payload');
Emit an event on method invocation
bob.setup.greet.toEmit('testing');
bob.on('testing', function() {
done();
});
bob.greet('bob');
Emit an event with args on method invocation
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');
Count the number of invocations of a method
var bob = new Person('bob');
bob = deride.wrap(bob);
bob.greet('alice');
bob.expect.greet.called.times(1);
Has convenience methods for invocation counts
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();
Handy lt
, lte
, gt
and gte
methods
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);
Determine if a method has never been called
var bob = new Person('bob');
bob = deride.wrap(bob);
bob.expect.greet.called.never();
Resetting the called count on all methods
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();
Determine if a method was called with a specific set of arguments
var bob = new Person('bob');
bob = deride.wrap(bob);
bob.greet('alice');
bob.greet('bob');
bob.expect.greet.called.withArgs('bob');
Determine if a method was called with the exact set of arguments
var bob = new Person('bob');
bob = deride.wrap(bob);
bob.greet('alice', ['james'], 987);
bob.expect.greet.called.matchExactly('alice', ['james'], 987);
Override the method body to change the invocation
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');
Override the return value for a function
var bob = new Person('bob');
bob = deride.wrap(bob);
bob.setup.greet.toReturn('foobar');
var result = bob.greet('alice');
result.should.eql('foobar');
Overriding the promise resolver for a function
To resolve with a value
var bob = new Person('bob');
bob = deride.wrap(bob);
bob.setup.greet.toResolveWith('foobar');
bob.greet('alice').then(function(result) {
result.should.eql('foobar');
});
To reject with a value
var bob = new Person('bob');
bob = deride.wrap(bob);
bob.setup.greet.toRejectWith('foobar');
bob.greet('alice').catch(function(result) {
result.should.eql('foobar');
});
Force a method invocation to throw a specific error
var bob = new Person('bob');
bob = deride.wrap(bob);
bob.setup.greet.toThrow('BANG');
should(function() {
bob.greet('alice');
}).
throw(/BANG/);
Override the invocation of a callback
when there is only one function passed as args
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');
});
when the callback is the last arg which is a 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();
});
Accelerating the timeout used internally by a function
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');
});
Setup an intercept
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'});
Setup for specific arguments
Setting the return value of a function when specific arguments are used
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');
Overriding a method's body when specific arguments are provided
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');
Throwing an error for a method invocation when specific arguments are provided
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/);
Override the invocation of a callback when specific arguments are provided
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');
});
});
Accelerating the timeout used internally by a function when specific arguments are provided
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();
});
});
Use a function as a predicate
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');
Setting multiple functions as a predicates
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');
Using a stub X times
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');
Using a stub when
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);
Specify to fallback
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);
Provide access to individual calls to a method
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');
Enable the assertion on a single arg being used in any invocation
when the arg is a primitive object
var bob = deride.wrap(bob);
bob.greet('alice', {
name: 'bob',
a: 1
}, 'sam');
bob.expect.greet.called.withArg('sam');
when the arg is not a primitive object
var bob = deride.wrap(bob);
bob.greet('alice', {
name: 'bob',
a: 1
});
bob.expect.greet.called.withArg({
name: 'bob'
});
when the arg is a primitive object
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/);
when the arg is not a primitive object
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);
Contributing
Please ensure that you run grunt
, have no style warnings and that all the tests are passing.
License
Copyright (c) 2014 Andrew Rea
Copyright (c) 2014 James Allen
Licensed under the MIT license.