From 8c2ab050a95e940c408cb9b337771d0fde12888d Mon Sep 17 00:00:00 2001 From: Wes Billman Date: Fri, 16 Jul 2021 14:58:16 -0700 Subject: [PATCH] Ensure observable methods return replay as needed - Remove unnecessary usage of expectations in non-async tests --- Snail/Observable.swift | 18 ++-- SnailTests/ObservableTests.swift | 162 +++++++++++++++++++------------ 2 files changed, 111 insertions(+), 69 deletions(-) diff --git a/Snail/Observable.swift b/Snail/Observable.swift index 540eb64..e778b4d 100644 --- a/Snail/Observable.swift +++ b/Snail/Observable.swift @@ -87,7 +87,7 @@ public class Observable: ObservableType { } public func map(_ transform: @escaping (T) -> U) -> Observable { - let transformed = Observable() + let transformed = Replay(1) subscribe( onNext: { value in @@ -105,7 +105,7 @@ public class Observable: ObservableType { } public func flatMap( _ transform: @escaping (T) -> Observable) -> Observable { - let flatMapped = Observable() + let flatMapped = Replay(1) subscribe( onNext: { value in @@ -124,7 +124,7 @@ public class Observable: ObservableType { } public func filter(_ isIncluded: @escaping (T) -> Bool) -> Observable { - let filtered = Observable() + let filtered = Replay(1) subscribe( onNext: { value in @@ -221,7 +221,7 @@ public class Observable: ObservableType { } public func skip(first: UInt) -> Observable { - let observable = Observable() + let observable = Replay(1) var count = first subscribe(onNext: { @@ -238,7 +238,7 @@ public class Observable: ObservableType { } public func take(first count: UInt) -> Observable { - let observable = Observable() + let observable = Replay(1) var taken = 0 subscribe(onNext: { @@ -285,7 +285,7 @@ public class Observable: ObservableType { } public static func merge(_ observables: [Observable]) -> Observable { - let latest = Observable() + let latest = Replay(1) observables.forEach { $0.forward(to: latest) } return latest } @@ -295,7 +295,7 @@ public class Observable: ObservableType { } public static func combineLatest(_ input1: Observable, _ input2: Observable) -> Observable<(T, U)> { - let combined = Observable<(T, U)>() + let combined = Replay<(T, U)>(1) var input1Result: (value: T?, isComplete: Bool) = (nil, false) var input2Result: (value: U?, isComplete: Bool) = (nil, false) @@ -339,7 +339,7 @@ public class Observable: ObservableType { public static func combineLatest(_ input1: Observable, _ input2: Observable, _ input3: Observable) -> Observable<(T, U, V)> { - let combined = Observable<(T, U, V)>() + let combined = Replay<(T, U, V)>(1) var input1Result: (value: T?, isComplete: Bool) = (nil, false) var input2Result: (value: U?, isComplete: Bool) = (nil, false) @@ -396,7 +396,7 @@ public class Observable: ObservableType { _ input2: Observable, _ input3: Observable, _ input4: Observable) -> Observable<(T, U, V, K)> { - let combined = Observable<(T, U, V, K)>() + let combined = Replay<(T, U, V, K)>(1) var input1Result: (value: T?, isComplete: Bool) = (nil, false) var input2Result: (value: U?, isComplete: Bool) = (nil, false) diff --git a/SnailTests/ObservableTests.swift b/SnailTests/ObservableTests.swift index 39c8532..349e226 100644 --- a/SnailTests/ObservableTests.swift +++ b/SnailTests/ObservableTests.swift @@ -257,7 +257,7 @@ class ObservableTests: XCTestCase { let observable = Observable() var received: [String] = [] - let exp = expectation(description: "debounce") + let exp = expectation(description: "throttle") let delay = 0.01 DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delay) { @@ -343,6 +343,12 @@ class ObservableTests: XCTestCase { XCTAssertEqual(done, true) } + func testSkipJust() { + var received = [Int]() + Just(1).skip(first: 0).subscribe(onNext: { received.append($0) }) + XCTAssertEqual(received.first, 1) + } + func testTakeFirst() { let observable = Observable() var received: [String] = [] @@ -376,6 +382,12 @@ class ObservableTests: XCTestCase { XCTAssertEqual(done, true) } + func testTakeJust() { + var received = [Int]() + Just(1).take(first: 1).subscribe(onNext: { received.append($0) }) + XCTAssertEqual(received.first, 1) + } + func testTakeDoneWhenCountIsReached() { let observable = Observable() var done = false @@ -467,6 +479,12 @@ class ObservableTests: XCTestCase { XCTAssertEqual(received.last, "2") } + func testMergeJust() { + var received = [Int]() + Observable.merge([Just(1)]).subscribe(onNext: { received.append($0) }) + XCTAssertEqual(received.first, 1) + } + func testCombineLatestNonOptional() { var received: [String] = [] @@ -521,6 +539,13 @@ class ObservableTests: XCTestCase { XCTAssertEqual(received[3], ": 3") } + func testCombineLatestJust() { + var received = (0, 0) + Observable.combineLatest(Just(1), Just(2)).subscribe(onNext: { received = ($0, $1) }) + XCTAssertEqual(received.0, 1) + XCTAssertEqual(received.1, 2) + } + func testCombineLatestError_firstMember() { var received: [String] = [] @@ -605,28 +630,34 @@ class ObservableTests: XCTestCase { XCTAssertEqual(received[0].2, 100.5) } + func testCombineLatest3Just() { + var received = (0, 0, 0) + Observable.combineLatest(Just(1), Just(2), Just(3)).subscribe(onNext: { received = ($0, $1, $2) }) + XCTAssertEqual(received.0, 1) + XCTAssertEqual(received.1, 2) + XCTAssertEqual(received.2, 3) + } + func testCombineLatest3Error_fromSecondMember() { let one = Observable() let two = Observable() let three = Observable() - let subject = Observable.combineLatest(one, two, three) + var error: TestError? - let exp = expectation(description: "combineLatest3 forwards error from observable") - subject.subscribe(onError: { _ in exp.fulfill() }) + Observable.combineLatest(one, two, three).subscribe(onError: { error = $0 as? TestError }) two.on(.error(TestError.test)) - waitForExpectations(timeout: 1) + XCTAssertEqual(error, .test) } func testCombineLatest3Error_fromThirdMember() { let one = Observable() let two = Observable() let three = Observable() - let subject = Observable.combineLatest(one, two, three) + var error: TestError? - let exp = expectation(description: "combineLatest3 forwards error from observable") - subject.subscribe(onError: { _ in exp.fulfill() }) + Observable.combineLatest(one, two, three).subscribe(onError: { error = $0 as? TestError }) three.on(.error(TestError.test)) - waitForExpectations(timeout: 1) + XCTAssertEqual(error, .test) } func testCombineLatest3Done_whenAllDone() { @@ -700,17 +731,25 @@ class ObservableTests: XCTestCase { XCTAssertEqual(received[0].3, "The other string") } + func testCombineLatest4Just() { + var received = (0, 0, 0, 0) + Observable.combineLatest(Just(1), Just(2), Just(3), Just(4)).subscribe(onNext: { received = ($0, $1, $2, $3) }) + XCTAssertEqual(received.0, 1) + XCTAssertEqual(received.1, 2) + XCTAssertEqual(received.2, 3) + XCTAssertEqual(received.3, 4) + } + func testCombineLatest4Error_fromFirstMember() { let one = Observable() let two = Observable() let three = Observable() let four = Observable() - let subject = Observable.combineLatest(one, two, three, four) + var error: TestError? - let exp = expectation(description: "combineLatest4 forwards error from observable") - subject.subscribe(onError: { _ in exp.fulfill() }) + Observable.combineLatest(one, two, three, four).subscribe(onError: { error = $0 as? TestError }) one.on(.error(TestError.test)) - waitForExpectations(timeout: 1) + XCTAssertEqual(error, .test) } func testCombineLatest4Error_fromSecondMember() { @@ -718,12 +757,11 @@ class ObservableTests: XCTestCase { let two = Observable() let three = Observable() let four = Observable() - let subject = Observable.combineLatest(one, two, three, four) + var error: TestError? - let exp = expectation(description: "combineLatest4 forwards error from observable") - subject.subscribe(onError: { _ in exp.fulfill() }) + Observable.combineLatest(one, two, three, four).subscribe(onError: { error = $0 as? TestError }) two.on(.error(TestError.test)) - waitForExpectations(timeout: 1) + XCTAssertEqual(error, .test) } func testCombineLatest4Error_fromThirdMember() { @@ -731,12 +769,11 @@ class ObservableTests: XCTestCase { let two = Observable() let three = Observable() let four = Observable() - let subject = Observable.combineLatest(one, two, three, four) + var error: TestError? - let exp = expectation(description: "combineLatest4 forwards error from observable") - subject.subscribe(onError: { _ in exp.fulfill() }) + Observable.combineLatest(one, two, three, four).subscribe(onError: { error = $0 as? TestError }) three.on(.error(TestError.test)) - waitForExpectations(timeout: 1) + XCTAssertEqual(error, .test) } func testCombineLatest4Error_fromFourthMember() { @@ -744,12 +781,11 @@ class ObservableTests: XCTestCase { let two = Observable() let three = Observable() let four = Observable() - let subject = Observable.combineLatest(one, two, three, four) + var error: TestError? - let exp = expectation(description: "combineLatest4 forwards error from observable") - subject.subscribe(onError: { _ in exp.fulfill() }) + Observable.combineLatest(one, two, three, four).subscribe(onError: { error = $0 as? TestError }) four.on(.error(TestError.test)) - waitForExpectations(timeout: 1) + XCTAssertEqual(error, .test) } func testCombineLatest4Done_whenAllDone() { @@ -818,26 +854,28 @@ class ObservableTests: XCTestCase { func testObservableMapError() { let observable = Observable() - let subject = observable.map { "Number: \($0)" } - - let exp = expectation(description: "observable map forwards error") - subject.subscribe(onError: { _ in exp.fulfill() }) + var error: TestError? + observable.map { "Number: \($0)" }.subscribe(onError: { error = $0 as? TestError }) observable.on(.error(TestError.test)) - waitForExpectations(timeout: 1) + XCTAssertEqual(error, .test) } func testObservableMapDone() { let observable = Observable() - let subject = observable.map { "Number: \($0)" } - - let exp = expectation(description: "observable map forwards done") - subject.subscribe(onDone: { exp.fulfill() }) + var done = false + observable.map { "Number: \($0)" }.subscribe(onDone: { done = true }) observable.on(.done) - waitForExpectations(timeout: 1) + XCTAssertEqual(done, true) + } + + func testObservableMapJust() { + var received: [Bool] = [] + Just(true).map { $0 }.subscribe(onNext: { received.append($0) }) + XCTAssertEqual(received.first, true) } func testObservableFilter() { @@ -857,26 +895,28 @@ class ObservableTests: XCTestCase { func testObservableFilterError() { let observable = Observable() - let subject = observable.filter { $0 % 2 == 0 } - - let exp = expectation(description: "observable filter forwards error") - subject.subscribe(onError: { _ in exp.fulfill() }) + var error: TestError? + observable.filter { $0 % 2 == 0 }.subscribe(onError: { error = $0 as? TestError }) observable.on(.error(TestError.test)) - waitForExpectations(timeout: 1) + XCTAssertEqual(error, .test) } func testObservableFilterDone() { let observable = Observable() - let subject = observable.filter { $0 % 2 == 0 } - - let exp = expectation(description: "observable filter forwards done") - subject.subscribe(onDone: { exp.fulfill() }) + var done = false + observable.filter { $0 % 2 == 0 }.subscribe(onDone: { done = true }) observable.on(.done) - waitForExpectations(timeout: 1) + XCTAssertEqual(done, true) + } + + func testObservableFilterJust() { + var received: [Bool] = [] + Just(true).filter { $0 }.subscribe(onNext: { received.append($0) }) + XCTAssertEqual(received.first, true) } func testObservableFlatMap() { @@ -892,24 +932,28 @@ class ObservableTests: XCTestCase { func testObservableFlatMapError() { let fetchTrigger = Observable() - let subject = fetchTrigger.flatMap { Variable(100).asObservable() } + var error: TestError? - let exp = expectation(description: "observable flatMap forwards error") - subject.subscribe(onError: { _ in exp.fulfill() }) + fetchTrigger.flatMap { Variable(100).asObservable() }.subscribe(onError: { error = $0 as? TestError }) fetchTrigger.on(.error(TestError.test)) - waitForExpectations(timeout: 1) + XCTAssertEqual(error, .test) } func testObservableFlatMapDone() { let fetchTrigger = Observable() - let subject = fetchTrigger.flatMap { Variable(100).asObservable() } + var done = false - let exp = expectation(description: "observable flatMap forwards done") - subject.subscribe(onDone: { exp.fulfill() }) + fetchTrigger.flatMap { Variable(100).asObservable() }.subscribe(onDone: { done = true }) fetchTrigger.on(.done) - waitForExpectations(timeout: 1) + XCTAssertEqual(done, true) + } + + func testObservableFlatMapJust() { + var received = [Int]() + Just(1).flatMap { _ in Variable(100).asObservable() }.subscribe(onNext: { received.append($0) }) + XCTAssertEqual(received.first, 100) } func testObservableDataRace() { @@ -1009,24 +1053,22 @@ class ObservableTests: XCTestCase { func testZipError() { let one = Observable() let two = Observable() + var error: TestError? - let subject = Observable.zip(one, two) - - let exp = expectation(description: "zip forwards error from observable") - subject.subscribe(onError: { _ in exp.fulfill() }) + Observable.zip(one, two).subscribe(onError: { error = $0 as? TestError }) one.on(.error(TestError.test)) - waitForExpectations(timeout: 1) + XCTAssertEqual(error, .test) } func testZipDone() { let one = Observable() let two = Observable() - var isDone = false - Observable.zip(one, two).subscribe(onDone: { isDone = true }) + Observable.zip(one, two).subscribe(onDone: { isDone = true }) one.on(.done) + XCTAssertTrue(isDone) } }