I dabble with RxSwift right now. Figuring out how to write tests for pure functions and reactive code, I tried to write an assertion for incoming events:

let expected: [Recorded<Event<URL?>>] = [next(0, nil), next(0, url)]
XCTAssertEqual(observer.events, expected)

Doesn’t compile because equating arrays with Optional<URL> in them won’t. In other words, [Recorded<Event<URL>>] (non-optional) would work.

I tries to figure out how to make this equatable, but then I stopped — why not lift the URL? into its own type? After all, the signal I’m observing is a path settings change. nil is allowed to signify removing the setting, like when you reset to defaults.

Lifting this into its own “either-or” type, also known as encapsulating the information in an enum, I end up with this:

enum URLChange {
case set(URL)
case unset
}

Making this equatable is simple. And the tests work, too. For the record, here’s the idiomatic Rx test:

func testArchiveURLChanges_RxVersion() {

let adapter = SettingsAdapter(defaults: UserDefaults.standard)
let url = URL(fileURLWithPath: "/a/url/", isDirectory: true)

let disposeBag = DisposeBag()
let scheduler = TestScheduler(initialClock: 0)
let observer = scheduler.createObserver(URLChange.self)

adapter.archiveURLChange()
.subscribe(observer)
.addDisposableTo(disposeBag)

defaults.set(url, forKey: "archiveUrl")

let expected: [Recorded<Event<URLChange>>] = [
next(0, .unset), // Initial value for fresh defaults
next(0, .set(url))
]
XCTAssertEqual(observer.events, expected)
}

And the traditional, asynchronous version I used before:

func testArchiveURLChanges() {

let adapter = SettingsAdapter(defaults: UserDefaults.standard)
let ex = expectation(description: "Reports back result")
let url = URL(fileURLWithPath: "/a/url/", isDirectory: true)
let disposeBag = DisposeBag()

adapter.archiveURLChange()
.subscribe(onNext: { if $0.url == url { ex.fulfill() } })
.addDisposableTo(disposeBag)

defaults.set(url, forKey: "archiveUrl")

waitForExpectations(timeout: 0.2, handler: nil)
}

I’m not too happy with the verbose setup of the Rx test case. But I’m refactoring the setup into — wait for it — the XCTestCase.setUp() method.

For simple assertions, the async variant would work just as well. Recording longer sequences of events with some timing is interesting, though I didn’t need that, yet, to test-drive my operators.

via Worklog of Christian Tietze http://ift.tt/2gLuXp5