Structure
UnimplementedScheduler
public struct UnimplementedScheduler<SchedulerTimeType, SchedulerOptions>: Scheduler
where
SchedulerTimeType: Strideable,
SchedulerTimeType.Stride: SchedulerTimeIntervalConvertible
A scheduler that causes the current XCTest test case to fail if it is used.
This scheduler can provide an additional layer of certainty that a tested code path does not require the use of a scheduler.
As a view model becomes more complex, only some of its logic may require a scheduler. When writing unit tests for any logic that does not require a scheduler, one should provide an unimplemented scheduler, instead. This documents, directly in the test, that the feature does not use a scheduler. If it did, or ever does in the future, the test will fail.
For example, the following view model has a couple responsibilities:
class EpisodeViewModel: ObservableObject {
@Published var episode: Episode?
let apiClient: ApiClient
let mainQueue: AnySchedulerOf<DispatchQueue>
init(apiClient: ApiClient, mainQueue: AnySchedulerOf<DispatchQueue>) {
self.apiClient = apiClient
self.mainQueue = mainQueue
}
func reloadButtonTapped() {
self.apiClient.fetchEpisode()
.receive(on: self.mainQueue)
.assign(to: &self.$episode)
}
func favoriteButtonTapped() {
self.episode?.isFavorite.toggle()
}
}
-
It lets the user tap a button to refresh some episode data
-
It lets the user toggle if the episode is one of their favorites
The API client delivers the episode on a background queue, so the view model must receive it on its main queue before mutating its state.
Tapping the favorite button, however, involves no scheduling. This means that a test can be written with an unimplemented scheduler:
func testFavoriteButton() {
let viewModel = EpisodeViewModel(
apiClient: .mock,
mainQueue: .unimplemented
)
viewModel.episode = .mock
viewModel.favoriteButtonTapped()
XCTAssert(viewModel.episode?.isFavorite == true)
viewModel.favoriteButtonTapped()
XCTAssert(viewModel.episode?.isFavorite == false)
}
With .unimplemented
, this test pretty strongly declares that favoriting an episode does not
need a scheduler to do the job, which means it is reasonable to assume that the feature is
simple and does not involve any asynchrony.
In the future, should favoriting an episode fire off an API request that involves a scheduler, this test will begin to fail, which is a good thing! This will force us to address the complexity that was introduced. Had we used any other scheduler, it would quietly receive this additional work and the test would continue to pass.
Relationships
Conforms To
Scheduler
Initializers
init(_:now:)
public init(_ prefix: String = "", now: SchedulerTimeType)
Creates an unimplemented scheduler with the given date.
Parameters
Name | Type | Description |
---|---|---|
prefix | String |
A string that identifies this scheduler and will prefix all failure messages. |
now | SchedulerTimeType |
now: The current date of the unimplemented scheduler. |
Properties
minimumTolerance
public var minimumTolerance: SchedulerTimeType.Stride
now
public var now: SchedulerTimeType
prefix
public let prefix: String
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