blob: ba353fadccd0c7699aad649aa369c4019f4bd14c [file] [log] [blame]
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/// A library that wraps [Timer] in a way that can be mocked out in test code.
/// Application code only needs to use [newTimer] to get an instance of [Timer].
/// Then test code can call [mock] to mock out all new [Timer] instances so that
/// they're controllable by a returned [Clock] object.
library mock_clock;
import 'dart:async';
import 'utils.dart';
/// The mock clock object. New [Timer]s will be mocked if and only if this is
/// non-`null`.
Clock _clock;
/// Whether or not mocking is active.
bool get _mocked => _clock != null;
/// Causes all future calls to [newTimer] to return mock [Timer] objects that
/// are controlled by the returned [Clock]. This may only be called once.
Clock mock() {
if (_mocked) {
throw new StateError("mock_clock.mock() has already been called.");
}
_clock = new Clock._();
return _clock;
}
typedef void TimerCallback();
/// Returns a new (possibly mocked) [Timer]. Works the same as [new Timer].
Timer newTimer(Duration duration, TimerCallback callback) =>
_mocked ? new _MockTimer(duration, callback) : new Timer(duration, callback);
/// A clock that controls when mocked [Timer]s move forward in time. It starts
/// at time 0 and advances forward millisecond-by-millisecond, broadcasting each
/// tick on the [onTick] stream.
class Clock {
/// The current time of the clock, in milliseconds. Starts at 0.
int get time => _time;
int _time = 0;
/// Controller providing streams for listening.
StreamController<int> _broadcastController =
new StreamController<int>.broadcast(sync: true);
Clock._();
/// The stream of millisecond ticks of the clock.
Stream<int> get onTick => _broadcastController.stream;
/// Advances the clock forward by [milliseconds]. This works like synchronous
/// code that takes [milliseconds] to execute; any [Timer]s that are scheduled
/// to fire during the interval will do so asynchronously once control returns
/// to the event loop.
void tick([int milliseconds = 1]) {
for (var i = 0; i < milliseconds; i++) {
var tickTime = ++_time;
runAsync(() {
_broadcastController.add(tickTime);
});
}
}
/// Automatically progresses forward in time as long as there are still
/// subscribers to [onTick] (that is, [Timer]s waiting to fire). After each
/// tick, this pumps the event loop repeatedly so that all non-clock-dependent
/// code runs before the next tick.
void run() {
pumpEventQueue().then((_) {
if (!_broadcastController.hasListener) return;
tick();
return run();
});
}
}
/// A mock implementation of [Timer] that uses [Clock] to keep time, rather than
/// the system clock.
class _MockTimer implements Timer {
/// The time at which the timer should fire.
final int _time;
/// The callback to run when the timer fires.
final TimerCallback _callback;
/// The subscription to the [Clock.onTick] stream.
StreamSubscription _subscription;
_MockTimer(Duration duration, this._callback)
: _time = _clock.time + duration.inMilliseconds {
_subscription = _clock.onTick.listen((time) {
if (time < _time) return;
_subscription.cancel();
_callback();
});
}
void cancel() => _subscription.cancel();
}