Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New materialize() operator over BlockingObservable #1383

Merged
merged 11 commits into from
Aug 22, 2017
Merged
20 changes: 19 additions & 1 deletion Documentation/UnitTests.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ It's easy to define `RxTests` extensions so you can write your tests in a readab

It is also possible to write integration tests by using `RxBlocking` operators.

Importing operators from `RxBlocking` library will enable blocking the current thread and wait for sequence results.
Importing operators from the `RxBlocking` library will enable blocking the current thread and wait for sequence results.

A simple way to test the result of your sequence is using the `toArray` method. It will return an array of all elements emitted once a sequence has completed successfully, or `throw` if an error caused the sequence to terminate.

```swift
let result = try fetchResource(location)
Expand All @@ -95,3 +97,19 @@ let result = try fetchResource(location)

XCTAssertEqual(result, expectedResult)
```

Another option is to use the `materialize` operator which allows you to more granularly examine your sequence. It will return a `MaterializedSequenceResult` enumeration that could be either `.completed` along with the emitted elements if the sequence completed successfully, or `failed` if the sequence terminated with an error, along with the emitted error.

```swift
let result = try fetchResource(location)
.toBlocking()
.materialize()

switch result {
case .completed:
XCTFail("Expected result to be complete eith error, but result was successful.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eith -> with?

case .failed(let elements, let error):
XCTAssertEqual(elements, expectedResult)
XCTAssertErrorEqual(error, expectedError)
}
```
44 changes: 36 additions & 8 deletions RxBlocking/BlockingObservable+Operators.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,20 @@
import RxSwift
#endif

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should document this, what completed means, what failed means and what is this enum for.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or your mean inline documentation block for that enum ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tweaked the UnitTests.md and added an example of the successful case, not just the error case.

Although if you mean inline docs like @freak4pc mentioned, I can add that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked through other parts of the RxSwift codebase and thought an inline comment describing the MaterializedSequenceResult was also appropriate, so I've added that also.

public enum MaterializedSequenceResult<T> {
case completed(elements: [T])
case failed(elements: [T], error: Error)
}

extension BlockingObservable {
/// Blocks current thread until sequence terminates.
///
/// If sequence terminates with error, terminating error will be thrown.
///
/// - returns: All elements of sequence.
public func toArray() throws -> [E] {
return try convertToArray()
let results = materializeResult()
return try elementsOrThrow(results)
}
}

Expand All @@ -28,7 +34,8 @@ extension BlockingObservable {
///
/// - returns: First element of sequence. If sequence is empty `nil` is returned.
public func first() throws -> E? {
return try convertToArray(max: 1).first
let results = materializeResult(max: 1)
return try elementsOrThrow(results).first
}
}

Expand All @@ -39,7 +46,8 @@ extension BlockingObservable {
///
/// - returns: Last element in the sequence. If sequence is empty `nil` is returned.
public func last() throws -> E? {
return try convertToArray().last
let results = materializeResult()
return try elementsOrThrow(results).last
}
}

Expand All @@ -60,7 +68,8 @@ extension BlockingObservable {
/// - parameter predicate: A function to test each source element for a condition.
/// - returns: Returns the only element of an sequence that satisfies the condition in the predicate, and reports an error if there is not exactly one element in the sequence.
public func single(_ predicate: @escaping (E) throws -> Bool) throws -> E? {
let elements = try convertToArray(max: 2, predicate: predicate)
let results = materializeResult(max: 2, predicate: predicate)
let elements = try elementsOrThrow(results)

switch elements.count {
case 0:
Expand All @@ -74,9 +83,19 @@ extension BlockingObservable {
}

extension BlockingObservable {
fileprivate func convertToArray(max: Int? = nil, predicate: @escaping (E) throws -> Bool = { _ in true }) throws -> [E] {
/// Blocks current thread until sequence terminates.
///
/// The sequence is materialized as a result type capturing how the sequence terminated (completed or error), along with any elements up to that point.
///
/// - returns: On completion, returns the list of elements in the sequence. On error, returns the list of elements up to that point, along with the error itself.
public func materialize() -> MaterializedSequenceResult<E> {
return materializeResult()
}
}

extension BlockingObservable {
fileprivate func materializeResult(max: Int? = nil, predicate: @escaping (E) throws -> Bool = { _ in true }) -> MaterializedSequenceResult<E> {
var elements: [E] = Array<E>()

var error: Swift.Error?

let lock = RunLoopLock(timeout: timeout)
Expand Down Expand Up @@ -127,9 +146,18 @@ extension BlockingObservable {
}

if let error = error {
throw error
return MaterializedSequenceResult.failed(elements: elements, error: error)
}

return elements
return MaterializedSequenceResult.completed(elements: elements)
}

fileprivate func elementsOrThrow(_ results: MaterializedSequenceResult<E>) throws -> [E] {
switch results {
case .failed(_, let error):
throw error
case .completed(let elements):
return elements
}
}
}
4 changes: 4 additions & 0 deletions RxBlocking/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ extension BlockingObservable {
public func single() throws -> E? {}
public func single(_ predicate: @escaping (E) throws -> Bool) throws -> E? {}
}

extension BlockingObservable {
public func materialize() -> MaterializedSequenceResult<E>
}
```


4 changes: 4 additions & 0 deletions Sources/AllTestz/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,10 @@ final class ObservableBlockingTest_ : ObservableBlockingTest, RxTestCase {
("testSingle_independent", ObservableBlockingTest.testSingle_independent),
("testSingle_timeout", ObservableBlockingTest.testSingle_timeout),
("testSinglePredicate_timeout", ObservableBlockingTest.testSinglePredicate_timeout),
("testMaterialize_empty", ObservableBlockingTest.testMaterialize_empty),
("testMaterialize_empty_fail", ObservableBlockingTest.testMaterialize_empty_fail),
("testMaterialize_someData", ObservableBlockingTest.testMaterialize_someData),
("testMaterialize_someData_fail", ObservableBlockingTest.testMaterialize_someData_fail),
] }
}

Expand Down
51 changes: 51 additions & 0 deletions Tests/RxBlockingTests/Observable+BlockingTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -337,3 +337,54 @@ extension ObservableBlockingTest {
}
}
}

// materialize

extension ObservableBlockingTest {
func testMaterialize_empty() {
let result = Observable<Int>.empty().toBlocking().materialize()

switch result {
case .completed(let elements):
XCTAssertEqual(elements, [])
case .failed:
XCTFail("Expected result to be complete successfully, but result was failed.")
}
}

func testMaterialize_empty_fail() {
let result = Observable<Int>.error(testError).toBlocking().materialize()

switch result {
case .completed:
XCTFail("Expected result to be complete eith error, but result was successful.")
case .failed(let elements, let error):
XCTAssertEqual(elements, [])
XCTAssertErrorEqual(error, testError)
}
}

func testMaterialize_someData() {
let result = Observable.of(42, 43, 44, 45).toBlocking().materialize()

switch result {
case .completed(let elements):
XCTAssertEqual(elements, [42, 43, 44, 45])
case .failed:
XCTFail("Expected result to be complete successfully, but result was failed.")
}
}

func testMaterialize_someData_fail() {
let sequence = Observable.concat(Observable.of(42, 43, 44, 45), Observable<Int>.error(testError))
let result = sequence.toBlocking().materialize()

switch result {
case .completed:
XCTFail("Expected result to be complete eith error, but result was successful.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo eith.

case .failed(let elements, let error):
XCTAssertEqual(elements, [42, 43, 44, 45])
XCTAssertErrorEqual(error, testError)
}
}
}