jsmock
Mocking framework for javascript, inspired by googlemock C++ framework.
This project is still under construction ...
Installation
jsmock
is published on npm
npm install --save-dev jsmock
User Guide
All examples provided below assume using mocha
/chai
test framework, although
jsmock
can be used with any framework of your choice.
Mock provides an API to define and verify expectations on all function calls
performed on reference to original object. A standard unit test involving mocks
will consist of following steps:
- Mock external dependencies of the test subject
- Define expectations on created mocks
- Execute actual tests on the subject
- Verify mock objects
- Cleanup
Creating Mocks
const Mock = require('jsmock').Mock;
let foo = {
bar: (a, b) => {
return a + b
}
};
let fooMock = new Mock(foo);
Now fooMock
is a mock object wrapping foo
. All functions of original object
have been replaced and any call to foo.bar
will cause an UnexpectedCall error
to be thrown.
expect(foo.bar.bind(foo)).to.throw(Error);
Defining Expectations
Expectation
is a main component of a mock oriented test. It describes what kind
of interactions with mocked object are expected during test execution. In addition
Expectation
also defines the actions that should be taken to mock the original
object behavior.
Expectation
for given function is defined by 3 objects:
- Matcher
- Cardinality
- Action List
Matcher decides if current function call is valid for the expectation. Usually
it means that function was called with expected argument list. Cardinality
describes how many times the expected call should occur. Consequently Action List
provides actions to be performed by mocked object for each call.
General Syntax
Expectation is created by calling expectCall
on mock object. Then it can be set up
with dedicated methods:
fooMock.expectCall('bar')
.matching(1, 4)
.times(4)
.willOnce((a, b) => a + b)
.willRepeatedly((a, b) => a - b);
Above code will create expectation on call to foo.bar(1,4)
that should happen
exactly 4 times. On the first call sum of the arguments will be returned, following
3 call with return their difference.
Specifying Matcher
Matcher is an object checking that call of mocked function is valid for given
expectation. If no explicit matcher is specified expectation will be executed for
any call to mocked function. Matcher can be specified as a predicate or simply as
an arguments list to be verified against actual call.
In jsmock matchers come in 3 flavours:
- Predicate Matcher - applies call arguments to provided predicate
- Strict Argument Matcher - call argument list must match exactly matcher argument list
- Weak Argument Matcher - first
N
call arguments must match matcher argument list
Matcher can be specified during Expectation
creation with expectCall
function,
or later with one of available Expectation
methods.
Method | Description |
---|
Mock.expectCall(fn, ...args) | Creates an expectation with Strict Argument Matcher from ...args |
Expectation.matching(...args) | Creates a Strict Argument Matcher from ...args |
Expectation.with(...args) | Alias of Expectation.matching |
Expectation.matchingAtLeast(...args) | Creates a Weak Argument Matcher from ...args |
Expectation.withAtLest(...args) | Alias of Expectation.matchingAtLeast |
Predicate Matcher is created automatically if one of above methods is called with
only one argument that happens to be a function.
fooMock.expectCall('bar', (a,b) => a > b);
foo.bar(3,2);
foo.bar(1,4);
fooMock.expectCall('bar', 1, 8);
foo.bar(1,8);
foo.bar(1,0);
fooMock.expectCall('bar').with(5,6);
foo.bar(5,6);
foo.bar(5,1);
fooMock.expectCall('bar').matchingAtLeast(3);
foo.bar(3,6);
foo.bar(3,9);
foo.bar(1,3);
Argument Type Matchers
jsmock comes with a family of predefined argument type matchers, that can be helpful
if we care more about the type of argument provided to the call than its actual value.
Matcher | Description |
---|
Matcher.ANY | Checks only presence of an argument in a call, doesn't care about actual type |
Matcher.OBJECT | Checks if given argument is an object |
Matcher.NUMBER | Checks if given argument is a number |
Matcher.STRING | Checks if given argument is a string |
Matcher.BOOLEAN | Checks if given argument is a boolean |
Matcher.FUNCTION | Checks if given argument is a function |
Matcher.ARRAY | Checks if given argument is an array |
const Matcher = require('jsmock').Matcher;
fooMock.expectCall('bar').with(Matcher.ANY, Matcher.NUMBER);
foo.bar(1,2);
foo.bar('a',2);
foo.bar([1,2,3], true);
Specifying Cardinality
Cardinality specifies number of expected calls to given function. jsmock
provides
two ways of specifying expectation cardinality. It can be provided explicitly
through one of expectation methods, or it can be calculated automatically from
list of specified actions. If cardinality is specified explicitly it takes precedence
over one calculated from action list.
Method | Description |
---|
Expectation.times(N) | Matching call should occur exactly N times |
Expectation.atLeast(N) | Matching call should occur at least N times |
Expectation.atMost(N) | Matching call should occur at least once and at most N times |
Expectation.between(M,N) | Matching call should occur at least M times and at most N times |
fooMock.expectCall('bar').times(2);
fooMock.expectCall('bar').atLeast(1);
fooMock.expectCall('bar').atMost(4);
fooMock.expectCall('bar').between(3,5);
fooMock.expectCall('bar').atMost(MaxCallCount);
fooMock.expectCall('bar').between(1, MaxCallCount);
Cardinality can be specified only once for given expectation.
Adding Actions
Action is an object encapsulating function to be executed instead of the original
mocked object code. Each expectation can have multiple actions defined with
specific cardinality. Actions are executed in the order of creation.
fooMock.expectCall('bar')
.willOnce((a,b) => a * b)
.willTwice((a,b) => a + b)
.willRepeatedly((a,b) => b);
If action specifying method is feed with function it will use it as a callback for
actual mocked function execution. If parameter of any other type is provided it will
be returned to the caller at execution time.
fooMock.expectCall('bar')
.willOnce(4)
.willTwice(7)
.willRepeatedly(0);
The willRepeatedly method specifies action with unlimited number of potential calls,
thus any other attempt to add more actions to the expectation will cause error. Also
note that willRepeatedly doesn't return expectation object so it isn't suitable for
chaining.
In nodejs
(and js
in general) it's very common to provide callback as the last argument
in the function call. Often the only purpose of the mock is to execute that callback with some
predefined arguments. This kind of action can be created easily using will...Invoke
versions
of action create methods
fsMock.expectCall('readdir')
.willOnceInvoke(null, ['a.js', 'b.js']);
fsMock.expectCall('readdir')
.willOnce((path, cb) => cb(null, ['a.js', 'b.js']));
Method | Description |
---|
Expectation.willOnce(k) | Adds an action to be executed once |
Expectation.willTwice(k) | Adds an action to be executed twice |
Expectation.willRepeatedly(k) | Adds an action to be executed until expectation cardinality is fulfilled |
Expectation.willOnceInvoke(...args) | Adds an invoker action to be executed once |
Expectation.willTwiceInvoke(...args) | Adds an invoker action to be executed twice |
Expectation.willRepeatedlyInvoke(...args) | Adds an invoker action to be executed until expectation cardinality is fulfilled |
Verifying Mocks
Mock object will yield errors directly in case of unexpected calls or violation of
cardinality upper bound (more calls than expected). Verification of cardinality
lower bound has to be done explicitly by the user, at the end of the test.
let foo = new Foo();
let fooMock = new Mock(foo);
fooMock.expectCall('bar')
.times(2)
.willRepeatedly(6);
expect(foo.bar()).to.be.equal(6);
fooMock.verify();
Call to verify methods cleans up all previously setup expectations.
Cleaning Mocks
Creation of the mock over an existing object modifies its functions. To restore
object to its original state you need to explicitly call cleanup method.
const fs = require('fs');
const Mock = require('jsmock').Mock;
let fsMock = new Mock(fs);
fsMock.expectCall('readdir')
.willOnce((path, cb) => cb(null, ['index.html']));
testedObject.doSomeStuff();
fsMock.verify();
fsMock.cleanup();
Examples
Some examples of potential "real life" usage can be found in
example.js test file.