Structure
ImmediateScheduler
public struct ImmediateScheduler<SchedulerTimeType, SchedulerOptions>: Scheduler
where
SchedulerTimeType: Strideable,
SchedulerTimeType.Stride: SchedulerTimeIntervalConvertible
A scheduler for performing synchronous actions.
You can only use this scheduler for immediate actions. If you attempt to schedule actions after a specific date, this scheduler ignores the date and performs them immediately.
This scheduler is useful for writing tests against publishers that use asynchrony operators,
such as receive(on:), subscribe(on:) and others, because it forces the publisher to emit
immediately rather than needing to wait for thread hops or delays using XCTestExpectation.
This scheduler is different from TestScheduler in that you cannot explicitly control how
time flows through your publisher, but rather you are instantly collapsing time into a single
point.
As a basic example, suppose you have a view model that loads some data after waiting for 10 seconds from when a button is tapped:
class HomeViewModel: ObservableObject {
@Published var episodes: [Episode]?
let apiClient: ApiClient
init(apiClient: ApiClient) {
self.apiClient = apiClient
}
func reloadButtonTapped() {
Just(())
.delay(for: .seconds(10), scheduler: DispatchQueue.main)
.flatMap { apiClient.fetchEpisodes() }
.assign(to: &self.episodes)
}
}
In order to test this code you would literally need to wait 10 seconds for the publisher to emit:
func testViewModel() {
let viewModel = HomeViewModel(apiClient: .mock)
viewModel.reloadButtonTapped()
_ = XCTWaiter.wait(for: [XCTestExpectation()], timeout: 10)
XCTAssert(viewModel.episodes, [Episode(id: 42)])
}
Alternatively, we can explicitly pass a scheduler into the view model initializer so that it can be controller from the outside:
class HomeViewModel: ObservableObject {
@Published var episodes: [Episode]?
let apiClient: ApiClient
let scheduler: AnySchedulerOf<DispatchQueue>
init(apiClient: ApiClient, scheduler: AnySchedulerOf<DispatchQueue>) {
self.apiClient = apiClient
self.scheduler = scheduler
}
func reloadButtonTapped() {
Just(())
.delay(for: .seconds(10), scheduler: self.scheduler)
.flatMap { self.apiClient.fetchEpisodes() }
.assign(to: &self.$episodes)
}
}
And then in tests use an immediate scheduler:
func testViewModel() {
let viewModel = HomeViewModel(
apiClient: .mock,
scheduler: .immediate
)
viewModel.reloadButtonTapped()
// No more waiting...
XCTAssert(viewModel.episodes, [Episode(id: 42)])
}
Note: This scheduler can not be used to test publishers with more complex timing logic, like those that use
Debounce,Throttle, orTimer.Publisher, and in factImmediateSchedulerwill not schedule this work in a defined way. Use aTestSchedulerinstead to capture your publisher's timing behavior.
Relationships
Conforms To
Scheduler
Initializers
init(now:)
public init(now: SchedulerTimeType)
Creates an immediate test scheduler with the given date.
Parameters
| Name | Type | Description |
|---|---|---|
| now | SchedulerTimeType |
The current date of the test scheduler. |
Properties
minimumTolerance
public let minimumTolerance: SchedulerTimeType.Stride = .zero
now
public let now: SchedulerTimeType
Methods
schedule(options:_:)
public func schedule(options _: SchedulerOptions?, _ action: () -> Void)
schedule(after:tolerance:options:_:)
public func schedule(
after _: SchedulerTimeType,
tolerance _: SchedulerTimeType.Stride,
options _: SchedulerOptions?,
_ action: () -> Void
)
schedule(after:interval:tolerance:options:_:)
public func schedule(
after _: SchedulerTimeType,
interval _: SchedulerTimeType.Stride,
tolerance _: SchedulerTimeType.Stride,
options _: SchedulerOptions?,
_ action: () -> Void
) -> Cancellable