@akromio/doubles
Doubles library to simulate objects:
You can create mocks by combining simulator and monitor.
Product of Spain, made in Valencia by Sia Codelabs.
Monitors
A monitor is an object to save the accesses to an object or the calls to a function.
Similar to the spies.
Object monitor
An object monitor monitors an object: its field accesses and its method calls.
We can use the monitor as the object being monitored.
We can create a monitor for an object as follows:
const {monitor} = require("@akromio/doubles");
const object = {};
const m = monitor(object);
const m = monitor(object, {onlyCalls: true});
const m = monitor(object, {members: ["member1", "member2"]});
const m = monitor(object, {members: ["method1", "method2"], onlyCalls: true});
console.log(m.x);
console.log(object.x);
When an object monitored, we have two kinds of entry in the log:
When a method is accessed, we can't forget that two operations are performed:
-
The access to the function object.
-
The function object call.
If only the method calls must be monitored, use the onlyCalls
option.
Function monitor
A function monitor monitors a function.
We can use the monitor as the function object being monitored.
We can create a monitor for a function object as follows:
const {monitor} = require("@akromio/doubles");
function fn() { }
const m = monitor(fn);
console.log(m(1, 2));
console.log(fn(1, 2));
Monitor clear
To clear a monitor:
const m = monitor({});
monitor.clear(m);
Monitor log
A monitor has a log, where the accesses and the calls are saved.
To get the log associated to a monitor, use the monitor.log()
function:
function log(m, opts?: {clear: boolean}): any
If clear
specified, monitor.clear(m)
is performed too.
Example:
const m = monitor({});
const log = monitor.log(m);
Operations with the log
log.calls
log.accesses
log.len
log.returns
log.returnedValue(value)
log.returnedType(Type)
log.raises
log.raisedValue(value)
log.raisedType(Type)
log.calledWith([arg1, arg2...])
log.getEntry(i)
log.getCall(i)
log.getAccess(i)
When getEntry()
, getCall()
or getAccess()
used, the instances returned are of the following types:
-
Access
, representing a field access:
Member | Date type | Description |
---|
member | string | Member name. |
value | any | Value returned or raised. |
returned | boolean | Did the access return a value? |
raised | boolean | Did the access raise an error? |
returnedValue(value) | boolean | Was the given value returned? |
returnedType(Type) | boolean | Is the returned value of the given type? |
raisedValue(value) | boolean | Was the given value raised? |
raisedType(Type) | boolean | Is the raised value of the given type? |
isGet() | boolean | Is a read access? |
isSet() | boolean | Is a write access? |
-
Call
, representing a function/method call:
Member | Date type | Description |
---|
value | any | Value returned or raised by the call. |
returned | boolean | Did the call return a value? |
raised | boolean | Did the call raise an error? |
returnedValue(value) | boolean | Was the given value returned? |
returnedType(Type) | boolean | Is the returned value of the given type? |
raisedValue(value) | boolean | Was the given value raised? |
raisedType(Type) | boolean | Is the raised value of the given type? |
calledWith([arg1, arg2...]) | boolean | Were the given arguments passed to the call? |
Examples:
const log = monitor.log(m);
expected(log.calls).equalTo(1);
expected(log.getCall(0)).toHave({value: 123});
Simulator
A simulator is an object to simulate another, defining only the responses.
Function simulator
A function simulator is a simulator for a function.
This is defined with the simulator.fun()
function:
simulator.fun(): function
simulator.fun(behavior: object[]|object): function
The behavior
is an object containing the responses for the calls.
If we always want to perform the same operation, we can use an object, as follows:
fn = simulator.fun({returns: 1234});
fn()
fn = simulator.fun({raises: new Error("my error")});
fn()
fn = simulator.fun({resolves: 1234});
await(fn())
fn = simulator.fun({rejects: new Error("my error")});
await(fn())
The following fields are possible:
Field | Data type | Description |
---|
returns | any | Value to return. |
raises | any | Value to raise. |
resolves | any | Value to return using a resolved promise. |
rejects | any | Value to raise using a rejected promise. |
invokes | function | Function to call, the value returned by this function will be the value returned. The arguments passed to the simulated function are passed to the function. |
But if we want to select among several possibilities, we have to use an array of objects.
Two types are possible: position-based or arguments-based.
On the one hand, we can use a position-based behavior, keeping in mind the arguments passed to the calls aren't used.
In this case, the first response must contain the i
field set to 0
.
Example:
const m = simulator.fun([
{i: 0, returns: 1234},
{returns: 5678},
{default: true, returns: 13579}
]);
m("one")
m("two")
m("three")
m("four")
The default response, defined with default
to true
, is used when call out of range.
If not defined, in the previous example, the third and fourth calls will raise an error.
On the other hand, we have the arguments-based behavior, using the arguments to select the response.
These are defined with an args
field.
Example:
fn = simulator.fun([
{default: true, returns: "default value"},
{args: [12, 34], returns: 1234},
{args: [34, 12], raises: new Error("my error")},
{args: [12, 34, 56], resolves: 135},
{args: [56, 34, 12], rejects: new Error("my error")}
]);
fn()
fn(34, 12)
fn(12, 34)
await(fn(56, 34, 12))
await(fn(12, 34, 56))
fn(1, 3, 5, 7, 9)
If the function simulator must simulate fields too, we can define its members in the second argument of simulator.fun()
:
fn = simulator.fun(
{returns: 1234},
{
x: simulator.field({returns: "value of x"}),
y: simulator.field({returns: "value of y"})
}
);
fn()
fn(12, 34)
fn.x
fn.y
Object simulator
An object simulator is a simulator for a (non-callable) object.
These are created with simulator()
:
const {simulator} = require("@akromio/doubles");
const {field, fun} = simulator;
const obj = simulator({
member1: field(),
member2: fun()
});
The fields must be defined with simulator.field()
and the methods with simulator.fun()
or its alias simulator.method()
.
Example:
const {simulator} = require("@akromio/doubles");
const {field, fun} = simulator;
const m = simulator({
x: field({returns: 12}),
y: field({raises: new Error("my error")}),
z: fun({returns: 1234})
});
m.x
m.y
m.z()
The fields can have position-based behaviors if needed, defined as seen with simulator.fun()
.
Special fields
simulator.field.uuid()
simulator.field.list(returns)
simulator.field.map(returns)
simulator.field.text(returns)
simulator.field.bool(returns)
simulator.field.any(returns)
Important. When a returns
passed, a clone of the value is used.
So well, the original value is not modified.