Skip to content

Commit

Permalink
lolex can now attach itself to the system timers and automatically ad… (
Browse files Browse the repository at this point in the history
#102)

lolex can now attach itself to the system timers and automatically advance
mocked time at a certain 'real' interval

- changed uninstall signature to carry over configuration flags in cases where this might be necessary
- updated shouldAdvanceTime functionality to support new install method signature
- moved time delta parameter to config
* Updated Readme.md and History.md
  • Loading branch information
acud authored and fatso83 committed Jul 14, 2017
1 parent 9d91fc8 commit 3775a00
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 17 deletions.
2 changes: 2 additions & 0 deletions History.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ v2.0.0 / 2017-07-13
* Add support for performance.now (#106)
* Fix issue with tick(): setSystemClock then throw
* Update old dependencies
* Added support to automatically increment time (#85)
* Changed internal uninstall method signature

v1.6.0 / 2017-02-25
===================
Expand Down
59 changes: 45 additions & 14 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ implementation that gets its time from the clock.

Lolex can be used to simulate passing time in automated tests and other
situations where you want the scheduling semantics, but don't want to actually
wait. Lolex is extracted from [Sinon.JS](https://github.com/sinonjs/sinon.js).
wait (however, from version 2.0 lolex supports those of you who would like to wait too).

Lolex is extracted from [Sinon.JS](https://github.com/sinonjs/sinon.js).

## Installation

Expand Down Expand Up @@ -80,7 +82,7 @@ var lolex = require("lolex");
var context = {
setTimeout: setTimeout // By default context.setTimeout uses the global setTimeout
}
var clock = lolex.install(context);
var clock = lolex.install({target: context});

context.setTimeout(fn, 15); // Schedules with clock.setTimeout

Expand All @@ -91,6 +93,35 @@ clock.uninstall();
Usually you want to install the timers onto the global object, so call `install`
without arguments.

#### Automatically incrementing mocked time
Since version 2.0 Lolex supports the possibility to attach the faked timers
to any change in the real system time. This basically means you no longer need
to `tick()` the clock in a situation where you won't know **when** to call `tick()`.

Please note that this is achieved using the original setImmediate() API at a certain
configurable interval `config.advanceTimeDelta` (default: 20ms). Meaning time would
be incremented every 20ms, not in real time.

An example would be:

```js
var lolex = require("lolex");
var clock = lolex.install({shouldAdvanceTime: true, advanceTimeDelta: 40});

setTimeout(() => {
console.log('this just timed out'); //executed after 40ms
}, 30);

setImmediate(() => {
console.log('not so immediate'); //executed after 40ms
});

setTimeout(() => {
console.log('this timed out after'); //executed after 80ms
clock.uninstall();
}, 50);
```

## API Reference

### `var clock = lolex.createClock([now[, loopLimit]])`
Expand All @@ -102,17 +133,17 @@ The `now` argument may be a number (in milliseconds) or a Date object.

The `loopLimit` argument sets the maximum number of timers that will be run when calling `runAll()` before assuming that we have an infinite loop and throwing an error. The default is `1000`.

### `var clock = lolex.install([options])`

Creates a clock and installs it onto the `options.target` object, or globally. This method swaps out timer APIs (like `setTimeout`) and replaces them with clock timers. A config option may be passed to `.install` containing the following properties:

### `var clock = lolex.install([config])`
Installs lolex using the specified config (otherwise with epoch `0` on the global scope). The following configuration options are available

`options` is an options object containing the following properties:
- `target` - the target to install timers in (default `window`)
- `now` - number or `Date` - a number (in milliseconds) or a Date object to start the clock with (default epoch)
- `toFake` - an array of the names of the methods that should be faked. You can pick from `setTimeout`, `clearTimeout`, `setImmediate`, `clearImmediate`, `setInterval`, `clearInterval`, and `Date`. E.g. `lolex.install(["setTimeout",
"clearTimeout"])`. (defaults to fake all timers)
- `loopLimit` - the maximum number of timers that will be run when calling runAll(). (default 1000)
Parameter | Type | Default | Description
--------- | ---- | ------- | ------------
`config.target`| Object | global | installs lolex onto the specified target context
`config.now` | Number/Date | 0 | installs lolex with the specified unix epoch
`config.toFake` | String[] | [] | an array with explicit function names to hijack. You can pick from `setTimeout`, `clearTimeout`, `setImmediate`, `clearImmediate`,`setInterval`, `clearInterval`, and `Date`. E.g. `lolex.install({ toFake: ["setTimeout","clearTimeout"]})`
`config.loopLimit` | Number | 1000 | the maximum number of timers that will be run when calling runAll()
`config.shouldAdvanceTime` | Boolean | false | tells lolex to increment mocked time automatically based on the real system time shift (e.g. the mocked time will be incremented by 20ms for every 20ms change in the real system time)
`config.advanceTimeDelta` | Number | 20 | relevant only when using with `shouldAdvanceTime: true`. increment mocked time by `advanceTimeDelta` ms every `advanceTimeDelta` ms change in the real system time.

### `var id = clock.setTimeout(callback, timeout)`

Expand Down Expand Up @@ -209,8 +240,8 @@ setSystemTime().

### `clock.uninstall()`

Restores the original methods on the `context` that was passed to
`lolex.install`, or the native timers if no `context` was given.
Restores the original methods on the `target` that was passed to
`lolex.install`, or the native timers if no `target` was given.

### `Date`

Expand Down
22 changes: 20 additions & 2 deletions src/lolex-src.js
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ function clearTimer(clock, timerId, ttype) {
}
}

function uninstall(clock, target) {
function uninstall(clock, target, config) {
var method,
i,
l;
Expand All @@ -361,6 +361,9 @@ function uninstall(clock, target) {
} else {
if (target[method] && target[method].hadOwnProperty) {
target[method] = clock["_" + method];
if (method === "clearInterval" && config.shouldAdvanceTime === true) {
target[method](clock.attachedInterval);
}
} else {
try {
delete target[method];
Expand Down Expand Up @@ -397,6 +400,10 @@ function hijackMethod(target, method, clock) {
target[method].clock = clock;
}

function doIntervalTick(clock, advanceTimeDelta) {
clock.tick(advanceTimeDelta);
}

var timers = {
setTimeout: setTimeout,
clearTimeout: clearTimeout,
Expand Down Expand Up @@ -636,16 +643,20 @@ exports.createClock = createClock;
* @param config.now {number|Date} a number (in milliseconds) or a Date object (default epoch)
* @param config.toFake {string[]} names of the methods that should be faked.
* @param config.loopLimit {number} the maximum number of timers that will be run when calling runAll()
* @param config.shouldAdvanceTime {Boolean} tells lolex to increment mocked time automatically (default false)
* @param config.advanceTimeDelta {Number} increment mocked time every <<advanceTimeDelta>> ms (default: 20ms)
*/
exports.install = function install(config) {
config = typeof config !== "undefined" ? config : {};
config.shouldAdvanceTime = config.shouldAdvanceTime || false;
config.advanceTimeDelta = config.advanceTimeDelta || 20;

var i, l;
var target = config.target || global;
var clock = createClock(config.now, config.loopLimit);

clock.uninstall = function () {
uninstall(clock, target);
uninstall(clock, target, config);
};

clock.methods = config.toFake || [];
Expand All @@ -660,6 +671,13 @@ exports.install = function install(config) {
hijackMethod(target.process, clock.methods[i], clock);
}
} else {
if (clock.methods[i] === "setInterval" && config.shouldAdvanceTime === true) {
var intervalTick = doIntervalTick.bind(null, clock, config.advanceTimeDelta);
var intervalId = target[clock.methods[i]](
intervalTick,
config.advanceTimeDelta);
clock.attachedInterval = intervalId;
}
hijackMethod(target, clock.methods[i], clock);
}
}
Expand Down
52 changes: 51 additions & 1 deletion test/lolex-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1683,7 +1683,6 @@ describe("lolex", function () {
}
if (performancePresent) {
it("replaces global performance.now", function () {

this.clock = lolex.install();
var prev = performance.now();
this.clock.tick(1000);
Expand Down Expand Up @@ -1803,6 +1802,57 @@ describe("lolex", function () {
});
});

describe("shouldAdvanceTime", function () {
it("should create an auto advancing timer", function (done) {
var testDelay = 29;
var date = new Date("2015-09-25");
var clock = lolex.install({now: date, shouldAdvanceTime: true});
assert.same(Date.now(), 1443139200000);
var timeoutStarted = Date.now();

setTimeout(function () {
var timeDifference = Date.now() - timeoutStarted;
assert.same(timeDifference, testDelay);
clock.uninstall();
done();
}, testDelay);
});

it("should test setImmediate", function (done) {
var date = new Date("2015-09-25");
var clock = lolex.install({now: date, shouldAdvanceTime: true});
assert.same(Date.now(), 1443139200000);
var timeoutStarted = Date.now();

setImmediate(function () {
var timeDifference = Date.now() - timeoutStarted;
assert.same(timeDifference, 0);
clock.uninstall();
done();
});
});

it("should test setInterval", function (done) {
var interval = 20;
var intervalsTriggered = 0;
var cyclesToTrigger = 3;
var date = new Date("2015-09-25");
var clock = lolex.install({now: date, shouldAdvanceTime: true});
assert.same(Date.now(), 1443139200000);
var timeoutStarted = Date.now();

var intervalId = setInterval(function () {
if (++intervalsTriggered === cyclesToTrigger) {
clearInterval(intervalId);
var timeDifference = Date.now() - timeoutStarted;
assert.same(timeDifference, interval * cyclesToTrigger);
clock.uninstall();
done();
}
}, interval);
});
});

if (performancePresent) {
describe("performance.now()", function () {
it("should start at 0", function () {
Expand Down

1 comment on commit 3775a00

@fatso83
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@acud Just a FYI that I removed some of the complexity of the implementation in #391 (due to #390). It seems it was unneeded.

Please sign in to comment.