diff --git a/src/util/timer.js b/src/util/timer.js
index 4ed1d82af..a5183ad11 100644
--- a/src/util/timer.js
+++ b/src/util/timer.js
@@ -90,6 +90,15 @@ class Timer {
     timeElapsed () {
         return this.nowObj.now() - this.startTime;
     }
+
+    /**
+     * Call a handler function after a specified amount of time has elapsed.
+     * @param {function} handler - function to call after the timeout
+     * @param {number} timeout - number of milliseconds to delay before calling the handler
+     */
+    setTimeout (handler, timeout) {
+        global.setTimeout(handler, timeout);
+    }
 }
 
 module.exports = Timer;
diff --git a/test/fixtures/mock-timer.js b/test/fixtures/mock-timer.js
new file mode 100644
index 000000000..3c9a3c77d
--- /dev/null
+++ b/test/fixtures/mock-timer.js
@@ -0,0 +1,121 @@
+/**
+ * Mimic the Timer class with external control of the "time" value, allowing tests to run more quickly and
+ * reliably. Multiple instances of this class operate independently: they may report different time values, and
+ * advancing one timer will not trigger timeouts set on another.
+ */
+class MockTimer {
+    /**
+     * Creates an instance of MockTimer.
+     * @param {*} [nowObj=null] - alert the caller that this parameter, supported by Timer, is not supported here.
+     * @memberof MockTimer
+     */
+    constructor (nowObj = null) {
+        if (nowObj) {
+            throw new Error('nowObj is not implemented in MockTimer');
+        }
+
+        /**
+         * The fake "current time" value, in epoch milliseconds.
+         * @type {number}
+         */
+        this._mockTime = 0;
+
+        /**
+         * Used to store the start time of a timer action.
+         * Updated when calling `timer.start`.
+         * @type {number}
+         */
+        this.startTime = 0;
+
+        /**
+         * Array of pending timeout callbacks
+         * @type {Array.<Object>}
+         */
+        this._timeouts = [];
+    }
+
+    /**
+     * Advance this MockTimer's idea of "current time", running timeout handlers if appropriate.
+     * @param {number} delta - the amount of time to add to the current mock time value, in milliseconds.
+     * @memberof MockTimer
+     */
+    advanceMockTime (delta) {
+        if (delta < 0) {
+            throw new Error('Time may not move backward');
+        }
+        this._mockTime += delta;
+        this._runTimeouts();
+    }
+
+    /**
+     * @returns {number} - current mock time elapsed since 1 January 1970 00:00:00 UTC.
+     * @memberof MockTimer
+     */
+    time () {
+        return this._mockTime;
+    }
+
+    /**
+     * Returns a time accurate relative to other times produced by this function.
+     * @returns {number} ms-scale accurate time relative to other relative times.
+     * @memberof MockTimer
+     */
+    relativeTime () {
+        return this._mockTime;
+    }
+
+    /**
+     * Start a timer for measuring elapsed time.
+     * @memberof MockTimer
+     */
+    start () {
+        this.startTime = this._mockTime;
+    }
+
+    /**
+     * @returns {number} - the time elapsed since `start()` was called.
+     * @memberof MockTimer
+     */
+    timeElapsed () {
+        return this._mockTime - this.startTime;
+    }
+
+    /**
+     * Call a handler function after a specified amount of time has elapsed.
+     * Guaranteed to happen in between "ticks" of JavaScript.
+     * @param {function} handler - function to call after the timeout
+     * @param {number} timeout - number of milliseconds to delay before calling the handler
+     * @memberof MockTimer
+     */
+    setTimeout (handler, timeout) {
+        this._timeouts.push({
+            time: this._mockTime + timeout,
+            handler
+        });
+        this._runTimeouts();
+    }
+
+    /**
+     * Run any timeout handlers whose timeouts have expired.
+     * @memberof MockTimer
+     */
+    _runTimeouts () {
+        const ready = [];
+        const waiting = [];
+
+        // partition timeout records by whether or not they're ready to call
+        this._timeouts.forEach(o => {
+            const isReady = o.time <= this._mockTime;
+            (isReady ? ready : waiting).push(o);
+        });
+
+        this._timeouts = waiting;
+
+        // next tick, call everything that's ready
+        global.setTimeout(() => {
+            ready.forEach(o => o.handler());
+        }, 0);
+    }
+}
+
+module.exports = MockTimer;
diff --git a/test/unit/util_mock-timer.js b/test/unit/util_mock-timer.js
new file mode 100644
index 000000000..e050bf116
--- /dev/null
+++ b/test/unit/util_mock-timer.js
@@ -0,0 +1,58 @@
+const test = require('tap').test;
+const MockTimer = require('../fixtures/mock-timer');
+
+test('spec', t => {
+    const timer = new MockTimer();
+
+    t.type(MockTimer, 'function');
+    t.type(timer, 'object');
+
+    t.type(timer.startTime, 'number');
+    t.type(timer.time, 'function');
+    t.type(timer.start, 'function');
+    t.type(timer.timeElapsed, 'function');
+
+    t.end();
+});
+
+test('time', t => {
+    const timer = new MockTimer();
+
+    const time1 = timer.time();
+    const time2 = timer.time();
+    timer.advanceMockTime(1);
+    const time3 = timer.time();
+
+    t.ok(time1 === time2);
+    t.ok(time2 < time3);
+    t.end();
+});
+
+test('start / timeElapsed', t => {
+    const timer = new MockTimer();
+    const halfDelay = 50;
+    const fullDelay = halfDelay + halfDelay;
+
+    timer.start();
+
+    let timeoutCalled = 0;
+
+    // Wait and measure timer
+    timer.setTimeout(() => {
+        t.ok(timeoutCalled === 0);
+        ++timeoutCalled;
+
+        const timeElapsed = timer.timeElapsed();
+        t.ok(timeElapsed === fullDelay);
+
+        t.end();
+    }, fullDelay);
+
+    // this should not call the callback
+    timer.advanceMockTime(halfDelay);
+
+    t.ok(timeoutCalled === 0);
+
+    // this should call the callback
+    timer.advanceMockTime(halfDelay);
+});
diff --git a/test/unit/util_timer.js b/test/unit/util_timer.js
index bf6d6b9ac..d58ca0152 100644
--- a/test/unit/util_timer.js
+++ b/test/unit/util_timer.js
@@ -32,7 +32,7 @@ test('start / timeElapsed', t => {
     timer.start();
 
     // Wait and measure timer
-    setTimeout(() => {
+    timer.setTimeout(() => {
         const timeElapsed = timer.timeElapsed();
         t.ok(timeElapsed >= 0);
         t.ok(timeElapsed >= (delay - threshold) &&