A Combine Scheduler that executes actions based on a virtual clock. Ideal for testing custom Publishers or testing time-dependent Pub-sub code.
The scheduler will run scheduled actions when they are due, based on a "virtual" clock. The schedulers clock can be adjusted using one of these methods.
run()
- run until there are no more actions scheduledstep()
- set the time to the due time of the first action that is dueadvanceTime(by:)
- advance the internal clock by a specified valuesetTime(to:)
- set the time to a value, the clock starts at.referenceTime
Actions can schedule other actions. The scheduler will run those actions if they are due, in the same time update call.
Combine provides scheduling publishers through these methods.
delay(for:tolerance:scheduler:options:)
debounce(for:scheduler:options:)
throttle(for:scheduler:latest:)
Tests for code that uses these methods will have to wait for the events to happen. When using the RunLoop or DispatchQueue schedulers, the test will take wall-clock time. Meaning that your test takes longer.
The VirtualTimeScheduler allows testing of such code without waiting in wall-clock time.
step()
finds the next action due and updates the time to that actions due
time and runs all actions due by that new time.
The run
method increases the virtual time step-wise until all scheduled
actions have completed.
"3 seconds later" refers to the "virtual time".
let scheduler = VirtualTimeScheduler()
let cancellable = Just(42)
.delay(for: .seconds(3), scheduler: scheduler)
.measureInterval(using: scheduler)
.sink { value in
print("Received \(value.magnitude) seconds later")
}
print("Before run \(Date())")
scheduler.run()
print("After run \(Date())")
// Before run 2022-01-20 14:13:11 +0000
// Received 3.0 seconds later
// After run 2022-01-20 14:13:11 +0000
The advanceTime(by:)
method advances the virtual time and runs
any scheduled actions that are due by the new time.
let scheduler = VirtualTimeScheduler()
let subject = PassthroughSubject<Int, Never>()
let cancellable = subject
.debounce(for: .seconds(1), scheduler: scheduler)
.sink { value in
let timeInterval = scheduler.now.timeIntervalSinceReferenceTime
print("Received \(value), \(timeInterval) seconds later")
}
for value in 0..<4 {
subject.send(value)
scheduler.advanceTime(by: .seconds(0.1))
}
scheduler.advanceTime(by: .seconds(1))
for value in 4..<8 {
subject.send(value)
scheduler.advanceTime(by: .seconds(0.1))
}
scheduler.advanceTime(by: .seconds(1))
_ = cancellable
// Prints:
// Received 3, 1.4 seconds later
// Received 7, 2.8 seconds later
It is also possible to set the time directly using setTime(to:)
.
Edit the Package.swift file. Add the VirtualTimeScheduler as a dependency:
let package = Package(
name: " ... ",
products: [ ... ],
dependencies: [
.package(url: "https://github.com/berikv/VirtualTimeScheduler.git", from: "1.0.0") // here
],
targets: [
.target(
name: " ... ",
dependencies: [
"VirtualTimeScheduler" // and here
]),
]
)
- Open menu File > Add Packages...
- Search for "https://github.com/berikv/VirtualTimeScheduler.git" and click Add Package.
- Open your project file, select your target in "Targets".
- Open Dependencies
- Click the + sign
- Add VirtualTimeScheduler