diff --git a/Demo/Demo.xcodeproj/project.pbxproj b/Demo/Demo.xcodeproj/project.pbxproj index 78d4d75..9e39b24 100644 --- a/Demo/Demo.xcodeproj/project.pbxproj +++ b/Demo/Demo.xcodeproj/project.pbxproj @@ -7,12 +7,14 @@ objects = { /* Begin PBXBuildFile section */ + 3A4288F2217D7B0000D3651D /* RxNimbleRxTestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4288F1217D7B0000D3651D /* RxNimbleRxTestTests.swift */; }; + 3A4288F4217D7B6200D3651D /* AnyError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4288F3217D7B6200D3651D /* AnyError.swift */; }; 5E47F32C1C3EAE9B00EC0751 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E47F32B1C3EAE9B00EC0751 /* AppDelegate.swift */; }; 5E47F32E1C3EAE9B00EC0751 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E47F32D1C3EAE9B00EC0751 /* ViewController.swift */; }; 5E47F3311C3EAE9B00EC0751 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5E47F32F1C3EAE9B00EC0751 /* Main.storyboard */; }; 5E47F3331C3EAE9B00EC0751 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5E47F3321C3EAE9B00EC0751 /* Assets.xcassets */; }; 5E47F3361C3EAE9B00EC0751 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5E47F3341C3EAE9B00EC0751 /* LaunchScreen.storyboard */; }; - 5E47F3411C3EAE9B00EC0751 /* DemoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E47F3401C3EAE9B00EC0751 /* DemoTests.swift */; }; + 5E47F3411C3EAE9B00EC0751 /* RxNimbleRxBlockingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E47F3401C3EAE9B00EC0751 /* RxNimbleRxBlockingTests.swift */; }; D04F8C922A4EA6727B35872D /* Pods_Demo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9265FBADB809B4B52B402D23 /* Pods_Demo.framework */; }; F189612069FC08D6409FD015 /* Pods_DemoTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 305E5AFA494DF2A10E0B8767 /* Pods_DemoTests.framework */; }; /* End PBXBuildFile section */ @@ -30,6 +32,8 @@ /* Begin PBXFileReference section */ 305E5AFA494DF2A10E0B8767 /* Pods_DemoTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DemoTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3388B98AFD3E9302DB045597 /* Pods-DemoTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DemoTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-DemoTests/Pods-DemoTests.debug.xcconfig"; sourceTree = ""; }; + 3A4288F1217D7B0000D3651D /* RxNimbleRxTestTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxNimbleRxTestTests.swift; sourceTree = ""; }; + 3A4288F3217D7B6200D3651D /* AnyError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyError.swift; sourceTree = ""; }; 5E47F3281C3EAE9B00EC0751 /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 5E47F32B1C3EAE9B00EC0751 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 5E47F32D1C3EAE9B00EC0751 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; @@ -38,7 +42,7 @@ 5E47F3351C3EAE9B00EC0751 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 5E47F3371C3EAE9B00EC0751 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 5E47F33C1C3EAE9B00EC0751 /* DemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 5E47F3401C3EAE9B00EC0751 /* DemoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoTests.swift; sourceTree = ""; }; + 5E47F3401C3EAE9B00EC0751 /* RxNimbleRxBlockingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RxNimbleRxBlockingTests.swift; sourceTree = ""; }; 5E47F3421C3EAE9B00EC0751 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9265FBADB809B4B52B402D23 /* Pods_Demo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Demo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 94276AA888ACDCBD57B00C60 /* Pods-Demo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Demo.release.xcconfig"; path = "Pods/Target Support Files/Pods-Demo/Pods-Demo.release.xcconfig"; sourceTree = ""; }; @@ -113,7 +117,9 @@ 5E47F33F1C3EAE9B00EC0751 /* DemoTests */ = { isa = PBXGroup; children = ( - 5E47F3401C3EAE9B00EC0751 /* DemoTests.swift */, + 3A4288F3217D7B6200D3651D /* AnyError.swift */, + 5E47F3401C3EAE9B00EC0751 /* RxNimbleRxBlockingTests.swift */, + 3A4288F1217D7B0000D3651D /* RxNimbleRxTestTests.swift */, 5E47F3421C3EAE9B00EC0751 /* Info.plist */, ); path = DemoTests; @@ -259,6 +265,7 @@ "${BUILT_PRODUCTS_DIR}/RxBlocking/RxBlocking.framework", "${BUILT_PRODUCTS_DIR}/RxNimble/RxNimble.framework", "${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework", + "${BUILT_PRODUCTS_DIR}/RxTest/RxTest.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( @@ -267,6 +274,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxBlocking.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxNimble.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxSwift.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxTest.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -307,7 +315,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 5E47F3411C3EAE9B00EC0751 /* DemoTests.swift in Sources */, + 3A4288F4217D7B6200D3651D /* AnyError.swift in Sources */, + 3A4288F2217D7B0000D3651D /* RxNimbleRxTestTests.swift in Sources */, + 5E47F3411C3EAE9B00EC0751 /* RxNimbleRxBlockingTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Demo/DemoTests/AnyError.swift b/Demo/DemoTests/AnyError.swift new file mode 100644 index 0000000..7b3c347 --- /dev/null +++ b/Demo/DemoTests/AnyError.swift @@ -0,0 +1,5 @@ +import Foundation + +enum AnyError: Error { + case any +} diff --git a/Demo/DemoTests/DemoTests.swift b/Demo/DemoTests/RxNimbleRxBlockingTests.swift similarity index 85% rename from Demo/DemoTests/DemoTests.swift rename to Demo/DemoTests/RxNimbleRxBlockingTests.swift index 6c706d2..a911f50 100644 --- a/Demo/DemoTests/DemoTests.swift +++ b/Demo/DemoTests/RxNimbleRxBlockingTests.swift @@ -3,16 +3,8 @@ import Nimble import RxSwift import RxNimble -class RxNimbleTest: QuickSpec { +class RxNimbleRxBlockingTests: QuickSpec { override func spec() { - /// A type-erased `Swift.Error` for testing purposes - struct AnyError: Swift.Error { - let message: String - init(_ message: String = "") { - self.message = message - } - } - //MARK: First describe("First") { it("works with plain observables") { @@ -39,7 +31,7 @@ class RxNimbleTest: QuickSpec { it("get first error") { let subject = ReplaySubject.createUnbounded() - subject.onError(AnyError()) + subject.onError(AnyError.any) expect(subject).first.to(throwError()) } @@ -64,7 +56,7 @@ class RxNimbleTest: QuickSpec { it("error, if terminated with error") { let subject = ReplaySubject.createUnbounded() subject.onNext("Hello, world!") - subject.onError(AnyError()) + subject.onError(AnyError.any) expect(subject).last.to(throwError()) } diff --git a/Demo/DemoTests/RxNimbleRxTestTests.swift b/Demo/DemoTests/RxNimbleRxTestTests.swift new file mode 100644 index 0000000..c205c8c --- /dev/null +++ b/Demo/DemoTests/RxNimbleRxTestTests.swift @@ -0,0 +1,106 @@ +import Quick +import Nimble +import RxSwift +import RxTest +import RxNimble + +class RxNimbleRxTestTests: QuickSpec { + override func spec() { + describe("Events") { + let initialClock = 0 + var scheduler: TestScheduler! + var disposeBag: DisposeBag! + + beforeEach { + disposeBag = DisposeBag() + scheduler = TestScheduler(initialClock: initialClock, simulateProcessingDelay: false) + } + + it("works with uncompleted streams") { + let subject = scheduler.createHotObservable([ + next(5, "Hello"), + next(10, "World"), + ]) + + expect(subject).events(scheduler: scheduler, disposeBag: disposeBag) + .to(equal([ + Recorded.next(5, "Hello"), + Recorded.next(10, "World") + ])) + } + + it("works with completed streams") { + let subject = scheduler.createHotObservable([ + next(5, "Hello"), + next(10, "World"), + completed(100) + ]) + + expect(subject).events(scheduler: scheduler, disposeBag: disposeBag) + .to(equal([ + Recorded.next(5, "Hello"), + Recorded.next(10, "World"), + Recorded.completed(100) + ])) + } + + it("works with errored streams") { + let subject: TestableObservable = scheduler.createHotObservable([ + error(5, AnyError.any) + ]) + + expect(subject).events(scheduler: scheduler, disposeBag: disposeBag) + .to(equal([ + Recorded.error(5, AnyError.any) + ])) + } + + it("throws error if any event is error") { + let subject = scheduler.createHotObservable([ + Recorded.next(5, "Hello"), + Recorded.next(10, "World"), + error(15, AnyError.any) + ]) + + expect(subject).events(scheduler: scheduler, disposeBag: disposeBag) + .to(throwError()) + } + + it("does not throw error if no errors") { + let subject = scheduler.createHotObservable([ + Recorded.next(5, "Hello"), + Recorded.next(10, "World") + ]) + + expect(subject).events(scheduler: scheduler, disposeBag: disposeBag) + .toNot(throwError()) + } + + it("subscribes at specified initial time") { + let initialTime = 50 + let eventTime = 100 + let subject = scheduler.createColdObservable([ + next(eventTime, "Hi") + ]) + + expect(subject).events(scheduler: scheduler, disposeBag: disposeBag, startAt: initialTime) + .to(equal([ + Recorded.next(initialTime + eventTime, "Hi") + ])) + } + + it("ignores hot stream events before initial time") { + let subject = scheduler.createHotObservable([ + next(5, "Hello"), + next(10, "World"), + completed(100) + ]) + + expect(subject).events(scheduler: scheduler, disposeBag: disposeBag, startAt: 15) + .to(equal([ + Recorded.completed(100) + ])) + } + } + } +} diff --git a/Demo/Podfile b/Demo/Podfile index 9537adb..b476642 100644 --- a/Demo/Podfile +++ b/Demo/Podfile @@ -12,7 +12,7 @@ target 'DemoTests' do pod 'Quick' pod 'Nimble' -pod 'RxNimble', path: '../' +pod 'RxNimble', subspecs: ['RxBlocking', 'RxTest'], path: '../' end diff --git a/Demo/Podfile.lock b/Demo/Podfile.lock index 4401694..8a598d0 100644 --- a/Demo/Podfile.lock +++ b/Demo/Podfile.lock @@ -3,20 +3,24 @@ PODS: - Quick (1.3.2) - RxBlocking (4.3.1): - RxSwift (~> 4.0) - - RxNimble (4.4.0): - - RxNimble/RxBlocking (= 4.4.0) - RxNimble/Core (4.4.0): - Nimble (~> 7.0) - RxSwift (~> 4.2) - RxNimble/RxBlocking (4.4.0): - RxBlocking - RxNimble/Core + - RxNimble/RxTest (4.4.0): + - RxNimble/Core + - RxTest - RxSwift (4.3.1) + - RxTest (4.3.1): + - RxSwift (~> 4.0) DEPENDENCIES: - Nimble - Quick - - RxNimble (from `../`) + - RxNimble/RxBlocking (from `../`) + - RxNimble/RxTest (from `../`) SPEC REPOS: https://github.com/cocoapods/specs.git: @@ -24,6 +28,7 @@ SPEC REPOS: - Quick - RxBlocking - RxSwift + - RxTest EXTERNAL SOURCES: RxNimble: @@ -35,7 +40,8 @@ SPEC CHECKSUMS: RxBlocking: 64c051285261ca2339481e91b5f70eb06b03660a RxNimble: 170cdfa19fb020c25608760961fd35053f2a2646 RxSwift: fe0fd770a43acdb7d0a53da411c9b892e69bb6e4 + RxTest: ea97a208826906f3904c0debdd09575ebcbfe216 -PODFILE CHECKSUM: 7405369509db0cbd242146add66181ab5ab0efb0 +PODFILE CHECKSUM: 41dbff57d3169ba9e4e1dd63856bcace5b3f96c1 COCOAPODS: 1.5.3 diff --git a/Source/RxTest/ThrowError+RxTest.swift b/Source/RxTest/ThrowError+RxTest.swift new file mode 100644 index 0000000..99e072b --- /dev/null +++ b/Source/RxTest/ThrowError+RxTest.swift @@ -0,0 +1,36 @@ +import Nimble +import RxSwift +import RxTest + +public func throwError() -> Predicate> { + func extractError(_ recorded: RecordedEvents?) -> [Error]? { + func extractError(_ recorded: Recorded>) -> Error? { + return recorded.value.error + } + + #if swift(>=4.1) + return recorded?.compactMap(extractError) + #else + return recorded?.flatMap(extractError) + #endif + } + + + return Predicate { actualEvents in + var actualError: Error? + do { + let recordedEvents = try actualEvents.evaluate() + if let error = extractError(recordedEvents)?.first { + throw error + } + } catch { + actualError = error + } + + if let actualError = actualError { + return PredicateResult(bool: true, message: .expectedCustomValueTo("throw any error", "<\(actualError)>")) + } else { + return PredicateResult(bool: false, message: .expectedCustomValueTo("throw any error", "no error")) + } + } +}