Zurvan is an asynchronous library for faking whole real-time dependency of node.js, mainly for test purposes.
Zurvan includes fake implementations for setTimeout
, clearTimeout
, setInterval
, clearInterval
,
Date.now
, Date
, process.uptime
and process.hrtime
. Also several other functions are taken into account,
such as setImmediate
, clearImmediate
and process.nextTick
, but they are not faked - library utilizes asynchronous
execution heavily (under assumption that since time is asynchronous by default, it's better to leave it this way).
Zurvan is currently not tested in the browser, so if you want to use it there, you can either hack it yourself (see: Zurvan requirements) or a contact me.
Multiple testcases cannot be ran in parallel when using Zurvan, as there is only a single time stream for forwarding.
Zurvan will NOT work properly (at least in release 0.2.1) if test code uses real I/O (filesystem, sockets etc.).
To be exact, waitForEmptyQueue
will not be able to work, since there will be no scheduled tasks on the queue, despite the fact that I/O is not done.
It is possible to use Zurvan in such cases, but additional Promise
s are required. It is generally preferred to use preloaded data and mock I/O via
usual async actions (setImmediate/process.nextTick
).
This is the main module of the library. Typical forwarding of time is done step by step:
- Event queue is cleared (all immediates and ticks are executed)
- Nearest timer is inspected - if it is after due time (now + requested advance), time is set to requested due time and forwarding ends
- Otherwise a single nearest timer is expired, and the 1. is applied again, with smaller reqested advance time
zurvan.blockSystem
is an exception, described below.
Library setup. Causes timers to be intercepted, i.e. all required functions are overridden after this call. Returns a Promise that is resolved when timers are faked and event queue is empty and rejected if interception was not possible (e.g. timers were already intercepted) It takes an optional configuration object as parameter, which takes precedence over global configuration. Details of configuration options are described in configuration documentation.
Resolution value is undefined
and rejection Error
with proper message.
Library teardown. Causes timers to be restored, i.e. all original functions are set back. Returns a Promise that is resolved when timers are faked and event queue is empty and rejected if interception was not possible (e.g. timers were already intercepted)
Resolution value is and object
, defined as:
{
timeouts: remainingTimeouts,
intervals: remainingIntervals,
date: zurvanEndDate,
processTime: zurvanEndProcessTime,
currentTime: zurvanEndTime
}
these fields are defined as:
remainingTimeouts
is an array ofTimer
objects, representing timeouts that did not expire yetremainingIntervals
is an array ofTimer
objects, representing intervals that did not expire yetzurvanEndDate
is zurvan date in same format that is returned when timers are intercepted (not exactly same as usualDate
- see [limitations]{#limitations})zurvanEndProcessTime
is zurvan process time inhrtime
format ([seconds, nanoseconds]
) at the release sitezurvanEndTime
isTimeUnit
that can be compared withdueTime
ofTimer
. It represents amount of time that was forwarded.
A Timer
object consists of at least two fields: callback
which is a 0-argument function executing what would be done if it expired and
dueTime
which is a TimeUnit
, informing when would the timer be expired. In case of intervals it must also contain callDelay
field, which is a TimeUnit
representing delay between consecutive calls. It may also contain arbitrary other fields, but they shall not be relied upon.
Order of elements in remainingTimeouts
and remainingIntervals
is undefined. If zurvan is executed with ignoreProcessTimers
or ignoreDate
configuration options,
respective fields (processTime
and date
) will not be available in releaseTimers
resolution.
If is rejected, rejection value is Error
with proper message.
Returns a new library object (new zurvan
instance) with modified default configuration.
This means that after calling .withDefaultConfiguration
, there are two instances of zurvan
. However, they should not be used in parallel,
i.e. only one of them should intercept timers at the same time. If another one already does, promise returned by interceptTimers
will be rejected.
Chain of withDefaultConfiguration(config)
causes all config
s to be merged (newer configurations override their fields, rest is taken from previous).
Configuration options are described in configuration documentation.
Returns a Promise
that is resolved when time is forwarded by given time and all timers with this dueTime are expired,
or rejected it time cannot be forwarded (e.g. timers were not intercepted yet).
Resolution value is undefined
and rejection Error
with proper message.
Argument may be either a number (it is then interpreted as millisecond) or a TimeUnit
object.
Simulates a blocking call - expires synchronously all timers up to due time at once, without actually executing them (during expiration).
Argument may be either a number (it is then interpreted as millisecond) or a TimeUnit
object.
To read about why is this function needed, and why does it require a synchronous API, see: blocking calls explaination.
Does not return anything. Throws if time cannot be forwarded (e.g. timers were not intercepted yet).
Sets values returned by new Date
and Date.now
at given point of time (returned values will be of course adjusted with advancing time).
Argument is expected to be "castable" to Date
- this means a Date
object, string
which is valid argument to Date.parse
or number
(which is then treated as timestamp).
Advances time up to the point when there is no timeout set any more. Intervals will remain.
Warning! Under certain circumstances this function may result in an infinite loop. Example:
function f() {
setTimeout(f, 100);
}
Returns a Promise
that is resolved when all timeouts are already called or rejected it time cannot be forwarded (e.g. timers were not intercepted yet).
Resolution value is undefined
and rejection Error
with proper message.
Forwards the time to the nearest timer and exipires all timers with same due time.
Resolution value is undefined
and rejection Error
with proper message.
Returns a Promise
that is resolved when all callbacks are executed and event queue is empty or rejected it time cannot be forwarded (e.g. timers were not intercepted yet)..
Returns a Promise
that is resolved when all immediates are already called or rejected it time cannot be forwarded (e.g. timers were not intercepted yet).
Also timers with zero time will be expired.
Resolution value is undefined
and rejection Error
with proper message.
A utility module providing time calculations that are - hopefully - more human-readable than operating on milliseconds everywhere.
Provide factory functions for TimeUnit
object, that represents time duration:
nanoseconds
microseconds
milliseconds
seconds
minutes
hours
days
weeks
TimeUnit
has the following API methods:
unit.extended(unit2)
- returns duration represented by sum of durations ofunit
andunit2
unit.shortened(unit2)
- returns duration represented by difference of durations betweenunit
andunit2
unit.add(unit2)
- mutator. Equal tounit.setTo(unit.extended(unit2))
unit.subtract(unit2)
- mutator. Equal tounit.setTo(unit.shortened(unit2))
unit.setTo(unit2)
- setsunit
duration to be equal to duration ofunit2
unit.copy()
- creates a deep copy ofunit
unit.isShorterThan(unit2)
- checks ifunit
represents shorter duration thanunit2
unit.isLongerThan(unit2)
- checks ifunit
represents longer duration thanunit2
unit.isEqualTo(unit2)
- checks if bothunit
andunit2
represent same duration, within a reasonable epsilon (current resolution is 10^-15 second)
All of them work only on TimeUnit
objects, but work smoothly on cross-unit basis. They do not take into account phenomenons such as leap seconds.
This is also the reason why units like month
and year
are not provided - because they would be ambigous and complicate the utility. To handle the calendar properly
much bigger library would need to be used.
There are no other API functions. All functions and modules in detail
directory are library internal and are not guaranteed to expose a stable set of methods. Please do not use them directly.
If you do - do it at your own risk. But if you do, and you find any of these functions useful (which I doubt - that's why they are in detail
), contact me to make it part of stable API.
After intercepting timers, Date
object is overridden (if ignoreDate
configuration option is set to false
). As a result, some external calls that rely on types may fail.
This is because for var d = new Date()
call Object.prototype.toString(d)
without zurvan
will return [object Date]
, and after timer interception, [object Object]
.
Please file an issue if this poses a problem for you.
For now, please refer to tests
directory for executable examples.
Obviously, JavaScript environment is much bigger than just Node.js, and you might need to fake timers in other environments, such as the browser. If you do, your environment has to fulfill several requirements:
- implement at least ECMAScript 5 (ES6 is better)
- have a basic implementation of promises (ES6 promises are sufficient or any basically compatible library - be careful though. Promises shall be implemented as microqueue tasks, or at least scheduled by global
setImmediate
- otherwise,zurvan
has to be started (not loaded - started. timers have to be intercepted) before promise library in every module they both occur. Additionally, promise library alone cannot be required by any file evaluated before first one requiring zurvan) For compatibility options with specific libraries (e.g. bluebird), see: compatibility options - implement
setImmediate/clearImmediate
- they cannot be implemented as wrappers oversetTimeout/clearTimeout
, at least for now. - implement
process.uptime
andprocess.hrtime
- if it doesn't, Zurvan has to be ran with compatibility option:ignoreProcessTimers: true
See configuration documentation to check out possible compatibility options (e.g. evaluating strings in setTimeout
)
Of course, if you have trouble with running Zurvan on your custom target, feel free to contact me for support
Be careful about scheduling - some asynchronous features used by browsers (such as MutationObserver
, postMessage
, requestAnimationFrame
) are not faked by zurvan
. Since this is only a time-faking library, it fakes only time-based async actions.
Additionally, Promise.resolve
and Promise.reject
are both specified to be executed asynchronously, but engine implementation is free to use either microqueue or macroqueue. Additionally, if it doesn't use API functions (setImmediate
) for
scheduling macroqueue tasks, then there will be cases where Zurvan won't behave correctly. Currently there are no such known cases for Node.js - and if they will be found, they are a bug and shall be fixed.
If you're trying to run on Node.js older than 0.10 - you will have trouble, as in these Nodes setImmediate
was not implemented and process.nextTick
was used to handle the macroqueue. However,
process.nextTick
is not a function faked by zurvan
. Again - contact me if you need support.
Zurvan is available as package on NPM
Name is taken after babilonian deity of infinite time, Zurvan. For more details see: https://en.wikipedia.org/wiki/Zurvanism
If you encouter a bug when using Zurvan, please report it as an issue on GitHub. Of course, if you are willing to issue a pull request, they are welcome.