CombineSchedulers Documentation

Structure Failing​Scheduler

public struct FailingScheduler<SchedulerTimeType, SchedulerOptions>: Scheduler
  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 a failing 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() {
      .receive(on: self.mainQueue)
      .assign(to: &self.$episode)

  func favoriteButtonTapped() {
  • 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 a failing scheduler:

func testFavoriteButton() {
  let viewModel = EpisodeViewModel(
    apiClient: .mock,
    mainQueue: .failing
  viewModel.episode = .mock

  XCTAssert(viewModel.episode?.isFavorite == true)

  XCTAssert(viewModel.episode?.isFavorite == false)

With .failing, 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.

%11 FailingScheduler FailingScheduler Scheduler Scheduler FailingScheduler->Scheduler

Conforms To




public init(_ prefix: String = "", now: SchedulerTimeType)  

Creates a failing test scheduler with the given date.


prefix String

A string that identifies this scheduler and will prefix all failure messages.

now Scheduler​Time​Type

now: The current date of the failing scheduler.



public var minimumTolerance: SchedulerTimeType.Stride  


public var now: SchedulerTimeType  


public let prefix: String



public func schedule(options _: SchedulerOptions?, _ action: () -> Void)  


public func schedule(
    after _: SchedulerTimeType,
    tolerance _: SchedulerTimeType.Stride,
    options _: SchedulerOptions?,
    _ action: () -> Void


public func schedule(
    after _: SchedulerTimeType,
    interval _: SchedulerTimeType.Stride,
    tolerance _: SchedulerTimeType.Stride,
    options _: SchedulerOptions?,
    _ action: () -> Void
  ) -> Cancellable