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 factImmediateScheduler
will not schedule this work in a defined way. Use aTestScheduler
instead 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