CombineSchedulers Documentation

Structure Immediate​Scheduler

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, or Timer.Publisher, and in fact ImmediateScheduler will not schedule this work in a defined way. Use a TestScheduler instead to capture your publisher's timing behavior.

%3 ImmediateScheduler ImmediateScheduler Scheduler Scheduler ImmediateScheduler->Scheduler

Conforms To

Scheduler

Initializers

init(now:​)

public init(now: SchedulerTimeType)  

Creates an immediate test scheduler with the given date.

Parameters

now Scheduler​Time​Type

The current date of the test scheduler.

Properties

minimum​Tolerance

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