amine2233 / CombineTestExtensions

A set of tools making writing tests for Apple's Combine framework easy.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Combine Test Extensions

Build Status SPM Compatible Platforms: macOS+iOS+tvOS Twitter


A set of tools making writing tests for Apple's Combine framework easy. Inspired by RxTest and RxBlocking.


Recorder

A Subscriber implementation which records all values from the attached publisher.

import CombineTestExtensions

let publisher = PassthroughSubject<Int, TestError>()
let recorder = publisher.record()

let queue = DispatchQueue.global(qos: .default)
queue.asyncAfter(deadline: .now() + 0.1) { publisher.send(1) }
queue.asyncAfter(deadline: .now() + 0.2) { publisher.send(2) }
queue.asyncAfter(deadline: .now() + 0.3) { publisher.send(completion: .finished) }

let records = recorder.waitAndCollectRecords()

XCTAssertEqual(records, [.value(1), .value(2), .completion(.finished)])

You can also specify the number of values you want to record. This is handy when the recorded publisher never completes.

import CombineTestExtensions

let publisher = PassthroughSubject<Int, Never>()
let recorder = publisher.record(numberOfRecords: 3)

let queue = DispatchQueue.global(qos: .default)
queue.asyncAfter(deadline: .now() + 0.1) { publisher.send(1) }
queue.asyncAfter(deadline: .now() + 0.2) { publisher.send(2) }
queue.asyncAfter(deadline: .now() + 0.3) { publisher.send(3) }
queue.asyncAfter(deadline: .now() + 0.4) { publisher.send(4) }

let records = recorder.waitAndCollectRecords()

XCTAssertEqual(records, [.value(1), .value(2), .value(3)])

TimedRecorder

TimedRecorder is a subclass of Recorder expanding its functionality by adding timestamps to recorded events. It's not that useful by itself but it really shines in combination with TestPublisher. To create a new TimerRecorder instance, you have to provide it with a TestScheduler:

let scheduler = TestScheduler()
let timedRecorder = some_Publisher.record(scheduler: scheduler)

TestScheduler

A Scheduler implementation for using in tests. It uses Int for its time type. The scheduler is inactive until resume() is called. It doesn't make much sense to use it alone. It's main purpose is to be used with TestPublisher and TimedRecorder.

This example explains how TestScheduler works:

import CombineTestExtensions

let scheduler = TestScheduler()

var result: [Int] = []

scheduler.schedule(after: 100) { result.append(3) }
scheduler.schedule(after: 50) { result.append(2) }
scheduler.schedule { result.append(1) }

scheduler.resume()

XCTAssertEqual(result, [1, 2, 3])

TestPublisher

TestPublisher allows you to schedule events to be sent at specific "virtual time" timestamps. It's the perfect match for TimedRecorder:

import CombineTestExtensions

let scheduler = TestScheduler()

let publisher: TestPublisher<Int, Never> = .init(scheduler, [
    (40, .completion(.finished)),
    (30, .value(300)),
    (20, .value(200)),
    (10, .value(100)),
])

let toString = publisher.map(String.init)

let records = toString
    .record(scheduler: scheduler)
    .waitAndCollectTimedRecords()

XCTAssertEqual(records, [
    (10, .value("100")),
    (20, .value("200")),
    (30, .value("300")),
    (40, .completion(.finished)),
])

Alternatives

If you need more advanced test tools for Combine, check out EntwineTest. It's almost a 1:1 re-implementation of RxTest with support for Combine-specific features like backpressure.

License and Credits

CombineTestExtensions is released under the MIT license. See LICENSE for details.

Created by Vojta Stavik @ Industrial Binaries.

About

A set of tools making writing tests for Apple's Combine framework easy.

License:MIT License


Languages

Language:Swift 100.0%