- test the behavior when a value arrives earlier than the subscription - test the behavior when a completion arrives earlier than the subscription - test the behavior when requesting before a subscription arrives - test the behavior when cancelling before a subscription arrives
465 lines
18 KiB
Swift
465 lines
18 KiB
Swift
//
|
|
// EncodeTests.swift
|
|
//
|
|
//
|
|
// Created by Joseph Spadafora on 6/21/19.
|
|
//
|
|
|
|
import XCTest
|
|
|
|
#if OPENCOMBINE_COMPATIBILITY_TEST
|
|
import Combine
|
|
#else
|
|
import OpenCombine
|
|
#endif
|
|
|
|
@available(macOS 10.15, iOS 13.0, *)
|
|
final class EncodeTests: XCTestCase {
|
|
|
|
private var encoder = TestEncoder()
|
|
private var decoder = TestDecoder()
|
|
|
|
override func setUp() {
|
|
super.setUp()
|
|
encoder = TestEncoder()
|
|
decoder = TestDecoder()
|
|
}
|
|
|
|
// MARK: - Encode
|
|
|
|
func testEncodingSuccess() throws {
|
|
|
|
let helper = OperatorTestHelper(
|
|
publisherType: CustomPublisherBase<[String : String], Error>.self,
|
|
initialDemand: .unlimited,
|
|
receiveValueDemand: .none,
|
|
createSut: { $0.encode(encoder: encoder) })
|
|
|
|
XCTAssertEqual(helper.publisher.send(["test": "test1"]), .none)
|
|
XCTAssertEqual(helper.publisher.send(["test": "test2"]), .none)
|
|
|
|
XCTAssertEqual(encoder.encoded[1] as? [String : String], ["test": "test1"])
|
|
XCTAssertEqual(encoder.encoded[2] as? [String : String], ["test": "test2"])
|
|
|
|
XCTAssertEqual(helper.tracking.history, [.subscription("Encode"),
|
|
.value(1),
|
|
.value(2)])
|
|
|
|
helper.publisher.send(completion: .finished)
|
|
|
|
XCTAssertEqual(helper.tracking.history, [.subscription("Encode"),
|
|
.value(1),
|
|
.value(2),
|
|
.completion(.finished)])
|
|
|
|
XCTAssertEqual(helper.publisher.send(testValue), .none)
|
|
|
|
XCTAssertEqual(helper.tracking.history, [.subscription("Encode"),
|
|
.value(1),
|
|
.value(2),
|
|
.completion(.finished)])
|
|
}
|
|
|
|
func testEncodingFailure() throws {
|
|
encoder.handleEncode = { _ in throw TestingError.oops }
|
|
try EncodeTests.testCodingFailure(expectedSubscription: "Encode",
|
|
{ $0.encode(encoder: encoder) })
|
|
}
|
|
|
|
func testEncodeSuccessHistory() throws {
|
|
let subject = PassthroughSubject<[String : String], Error>()
|
|
let publisher = subject.encode(encoder: encoder)
|
|
let subscriber = TrackingSubscriberBase<Int, Error>(
|
|
receiveSubscription: { $0.request(.unlimited) }
|
|
)
|
|
|
|
publisher.subscribe(subscriber)
|
|
subject.send(testValue)
|
|
|
|
guard let testKey = encoder.encoded.first?.key, encoder.encoded.count == 1 else {
|
|
XCTFail("Could not get testing data from encoding")
|
|
return
|
|
}
|
|
XCTAssertEqual(subscriber.history, [.subscription("Encode"),
|
|
.value(testKey)])
|
|
}
|
|
|
|
func testEncodeDemand() throws {
|
|
try EncodeTests.testDemand {
|
|
$0.encode(encoder: encoder)
|
|
}
|
|
}
|
|
|
|
func testEncodeReceiveSubscriptionTwice() throws {
|
|
try testReceiveSubscriptionTwice { $0.encode(encoder: encoder) }
|
|
}
|
|
|
|
func testEncodeCancelsSubscriptionThenReleasesIt() throws {
|
|
try EncodeTests.testCancelsSubscriptionThenReleasesIt(
|
|
expectedSubscription: "Encode",
|
|
{ $0.encode(encoder: encoder) }
|
|
)
|
|
}
|
|
|
|
func testEncodeCompletionReleasesUpstreamSubscription() throws {
|
|
try EncodeTests.testCompletionReleasesUpstreamSubscription(
|
|
expectedSubscription: "Encode",
|
|
{ $0.encode(encoder: encoder) }
|
|
)
|
|
}
|
|
|
|
func testEncodeCancellingSubscriptionPreventsDeliveringToDownstream() throws {
|
|
try EncodeTests.testCancellingSubscriptionPreventsDeliveringToDownstream(
|
|
expectedSubscription: "Encode",
|
|
{ $0.encode(encoder: encoder) }
|
|
)
|
|
}
|
|
|
|
func testEncodeReceiveValueBeforeSubscription() {
|
|
testReceiveValueBeforeSubscription(value: 0,
|
|
expected: .history([.value(1)],
|
|
demand: .max(42)),
|
|
{ $0.encode(encoder: encoder) })
|
|
}
|
|
|
|
func testEncodeReceiveCompletionBeforeSubscription() {
|
|
testReceiveCompletionBeforeSubscription(
|
|
inputType: Int.self,
|
|
expected: .history([.completion(.finished)]),
|
|
{ $0.encode(encoder: encoder) }
|
|
)
|
|
}
|
|
|
|
func testEncodeRequestBeforeSubscription() {
|
|
testRequestBeforeSubscription(inputType: Int.self,
|
|
shouldCrash: false,
|
|
{ $0.encode(encoder: encoder) })
|
|
}
|
|
|
|
func testEncodeCancelBeforeSubscription() {
|
|
testCancelBeforeSubscription(inputType: Int.self,
|
|
shouldCrash: false,
|
|
{ $0.encode(encoder: encoder) })
|
|
}
|
|
|
|
func testEncodeLifecycle() throws {
|
|
try testLifecycle(sendValue: 31,
|
|
cancellingSubscriptionReleasesSubscriber: false,
|
|
{ $0.encode(encoder: TestEncoder()) })
|
|
}
|
|
|
|
func testEncodeReflection() throws {
|
|
try testReflection(parentInput: Int.self,
|
|
parentFailure: Error.self,
|
|
description: "Encode",
|
|
customMirror: expectedChildren(
|
|
("downstream", .contains("TrackingSubscriberBase")),
|
|
("finished", "false"),
|
|
("upstreamSubscription", "nil")
|
|
),
|
|
playgroundDescription: "Encode",
|
|
{ $0.encode(encoder: encoder) })
|
|
}
|
|
|
|
// MARK: - Decode
|
|
|
|
func testDecodingSuccess() throws {
|
|
decoder.handleDecode = { ["key\($0)" : "value\($0)"] }
|
|
|
|
let helper = OperatorTestHelper(
|
|
publisherType: CustomPublisher.self,
|
|
initialDemand: .unlimited,
|
|
receiveValueDemand: .none,
|
|
createSut: { $0.decode(type: [String : String].self, decoder: decoder) })
|
|
|
|
XCTAssertEqual(helper.publisher.send(1), .none)
|
|
XCTAssertEqual(helper.publisher.send(2), .none)
|
|
|
|
XCTAssertEqual(helper.tracking.history, [.subscription("Decode"),
|
|
.value(["key1" : "value1"]),
|
|
.value(["key2" : "value2"])])
|
|
|
|
helper.publisher.send(completion: .finished)
|
|
|
|
XCTAssertEqual(helper.publisher.send(3), .none)
|
|
|
|
XCTAssertEqual(helper.tracking.history, [.subscription("Decode"),
|
|
.value(["key1" : "value1"]),
|
|
.value(["key2" : "value2"]),
|
|
.completion(.finished)])
|
|
}
|
|
|
|
func testDecodingFailure() throws {
|
|
decoder.handleDecode = { _ in throw TestingError.oops }
|
|
try EncodeTests.testCodingFailure(
|
|
expectedSubscription: "Decode",
|
|
{ $0.decode(type: [String : String].self, decoder: decoder) }
|
|
)
|
|
}
|
|
|
|
func testDecodeDemand() throws {
|
|
decoder.handleDecode = { _ in testValue }
|
|
try EncodeTests.testDemand {
|
|
$0.decode(type: [String : String].self, decoder: decoder)
|
|
}
|
|
}
|
|
|
|
func testDecodeReceiveSubscriptionTwice() throws {
|
|
try testReceiveSubscriptionTwice {
|
|
$0.decode(type: [String : String].self, decoder: decoder)
|
|
}
|
|
}
|
|
|
|
func testDecodeCancelsSubscriptionThenReleasesIt() throws {
|
|
decoder.handleDecode = { _ in testValue }
|
|
try EncodeTests.testCancelsSubscriptionThenReleasesIt(
|
|
expectedSubscription: "Decode",
|
|
{ $0.decode(type: [String : String].self, decoder: decoder) }
|
|
)
|
|
}
|
|
|
|
func testDecodeCompletionReleasesUpstreamSubscription() throws {
|
|
decoder.handleDecode = { _ in testValue }
|
|
try EncodeTests.testCompletionReleasesUpstreamSubscription(
|
|
expectedSubscription: "Decode",
|
|
{ $0.decode(type: [String : String].self, decoder: decoder) }
|
|
)
|
|
}
|
|
|
|
func testDecodeCancellingSubscriptionPreventsDeliveringToDownstream() throws {
|
|
decoder.handleDecode = { _ in testValue }
|
|
try EncodeTests.testCancellingSubscriptionPreventsDeliveringToDownstream(
|
|
expectedSubscription: "Decode",
|
|
{ $0.decode(type: [String : String].self, decoder: decoder) }
|
|
)
|
|
}
|
|
|
|
func testDecodeReceiveValueBeforeSubscription() {
|
|
testReceiveValueBeforeSubscription(
|
|
value: 0,
|
|
expected: .history([.completion(.failure(TestDecoder.error))], demand: .none),
|
|
{ $0.decode(type: String.self, decoder: decoder) }
|
|
)
|
|
}
|
|
|
|
func testDecodeReceiveCompletionBeforeSubscription() {
|
|
testReceiveCompletionBeforeSubscription(
|
|
inputType: Int.self,
|
|
expected: .history([.completion(.finished)]),
|
|
{ $0.decode(type: String.self, decoder: decoder) }
|
|
)
|
|
}
|
|
|
|
func testDecodeRequestBeforeSubscription() {
|
|
testRequestBeforeSubscription(inputType: Int.self,
|
|
shouldCrash: false,
|
|
{ $0.decode(type: String.self, decoder: decoder) })
|
|
}
|
|
|
|
func testDecodeCancelBeforeSubscription() {
|
|
testCancelBeforeSubscription(inputType: Int.self,
|
|
shouldCrash: false,
|
|
{ $0.decode(type: String.self, decoder: decoder) })
|
|
}
|
|
|
|
func testDecodeLifecycle() throws {
|
|
try testLifecycle(sendValue: 31,
|
|
cancellingSubscriptionReleasesSubscriber: false,
|
|
{ $0.decode(type: Int.self, decoder: decoder) })
|
|
}
|
|
|
|
func testDecodeReflection() throws {
|
|
try testReflection(parentInput: Int.self,
|
|
parentFailure: Error.self,
|
|
description: "Decode",
|
|
customMirror: expectedChildren(
|
|
("downstream", .contains("TrackingSubscriberBase")),
|
|
("finished", "false"),
|
|
("upstreamSubscription", "nil")
|
|
),
|
|
playgroundDescription: "Decode",
|
|
{ $0.decode(type: Int.self, decoder: decoder) })
|
|
}
|
|
|
|
// MARK: - Generic tests
|
|
|
|
private static func testCodingFailure<Operator: Publisher>(
|
|
expectedSubscription: StringSubscription,
|
|
_ makeOperator: (CustomPublisher) -> Operator
|
|
) throws where Operator.Output: Equatable, Operator.Failure == Error {
|
|
let helper = OperatorTestHelper(
|
|
publisherType: CustomPublisher.self,
|
|
initialDemand: .unlimited,
|
|
receiveValueDemand: .none,
|
|
createSut: makeOperator
|
|
)
|
|
|
|
XCTAssertEqual(helper.tracking.history, [.subscription(expectedSubscription)])
|
|
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
|
|
|
helper.subscription.onCancel = {
|
|
helper.downstreamSubscription?.request(.max(42))
|
|
|
|
XCTAssertEqual(helper.tracking.history, [.subscription(expectedSubscription)])
|
|
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
|
.cancelled,
|
|
.requested(.max(42))])
|
|
}
|
|
|
|
helper.tracking.onFailure = { error in
|
|
XCTAssertEqual(helper.tracking.history,
|
|
[.subscription(expectedSubscription),
|
|
.completion(.failure(error))])
|
|
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
|
.cancelled,
|
|
.requested(.max(42))])
|
|
}
|
|
|
|
XCTAssertEqual(helper.publisher.send(1), .none)
|
|
XCTAssertEqual(helper.publisher.send(2), .none)
|
|
|
|
XCTAssertEqual(helper.tracking.history,
|
|
[.subscription(expectedSubscription),
|
|
.completion(.failure(TestingError.oops))])
|
|
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
|
.cancelled,
|
|
.requested(.max(42))])
|
|
}
|
|
|
|
private static func testDemand<Operator: Publisher>(
|
|
_ makeOperator: (CustomPublisherBase<Int, Error>) -> Operator
|
|
) throws {
|
|
let helper = OperatorTestHelper(
|
|
publisherType: CustomPublisherBase<Int, Error>.self,
|
|
initialDemand: .max(37),
|
|
receiveValueDemand: .max(2),
|
|
createSut: makeOperator
|
|
)
|
|
|
|
XCTAssertEqual(helper.publisher.send(37), .max(2))
|
|
XCTAssertEqual(helper.subscription.history, [.requested(.max(37))])
|
|
|
|
try XCTUnwrap(helper.downstreamSubscription).cancel()
|
|
|
|
XCTAssertEqual(helper.publisher.send(42), .none)
|
|
XCTAssertEqual(helper.subscription.history, [.requested(.max(37)), .cancelled])
|
|
}
|
|
|
|
private static func testReceiveSubscriptionTwice<Operator: Publisher>(
|
|
_ makeOperator: (CustomPublisher) -> Operator
|
|
) throws where Operator.Output: Equatable {
|
|
let helper = OperatorTestHelper(
|
|
publisherType: CustomPublisher.self,
|
|
initialDemand: nil,
|
|
receiveValueDemand: .none,
|
|
createSut: makeOperator
|
|
)
|
|
|
|
XCTAssertEqual(helper.subscription.history, [])
|
|
|
|
let secondSubscription = CustomSubscription()
|
|
|
|
try XCTUnwrap(helper.publisher.subscriber)
|
|
.receive(subscription: secondSubscription)
|
|
|
|
XCTAssertEqual(secondSubscription.history, [.cancelled])
|
|
|
|
try XCTUnwrap(helper.publisher.subscriber)
|
|
.receive(subscription: helper.subscription)
|
|
|
|
XCTAssertEqual(helper.subscription.history, [.cancelled])
|
|
|
|
try XCTUnwrap(helper.downstreamSubscription).cancel()
|
|
|
|
XCTAssertEqual(helper.subscription.history, [.cancelled, .cancelled])
|
|
|
|
let thirdSubscription = CustomSubscription()
|
|
|
|
try XCTUnwrap(helper.publisher.subscriber)
|
|
.receive(subscription: thirdSubscription)
|
|
|
|
XCTAssertEqual(thirdSubscription.history, [.cancelled])
|
|
}
|
|
|
|
private static func testCancelsSubscriptionThenReleasesIt<Operator: Publisher>(
|
|
expectedSubscription: StringSubscription,
|
|
_ makeOperator: (CustomPublisher) -> Operator
|
|
) throws where Operator.Output: Equatable {
|
|
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
|
|
initialDemand: nil,
|
|
receiveValueDemand: .none,
|
|
createSut: makeOperator)
|
|
|
|
XCTAssertEqual(helper.tracking.history, [.subscription(expectedSubscription)])
|
|
XCTAssertEqual(helper.subscription.history, [])
|
|
|
|
var recursionDepth = 0
|
|
helper.subscription.onCancel = {
|
|
if recursionDepth >= 5 { return }
|
|
recursionDepth += 1
|
|
helper.downstreamSubscription?.cancel()
|
|
}
|
|
|
|
try XCTUnwrap(helper.downstreamSubscription).cancel()
|
|
|
|
XCTAssertEqual(recursionDepth, 5)
|
|
}
|
|
|
|
private static func testCancellingSubscriptionPreventsDeliveringToDownstream<
|
|
Operator: Publisher
|
|
>(
|
|
expectedSubscription: StringSubscription,
|
|
_ makeOperator: (CustomPublisher) -> Operator
|
|
) throws where Operator.Output: Equatable {
|
|
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
|
|
initialDemand: nil,
|
|
receiveValueDemand: .none,
|
|
createSut: makeOperator)
|
|
|
|
XCTAssertEqual(helper.tracking.history, [.subscription(expectedSubscription)])
|
|
XCTAssertEqual(helper.subscription.history, [])
|
|
|
|
try XCTUnwrap(helper.downstreamSubscription).cancel()
|
|
try XCTUnwrap(helper.downstreamSubscription).cancel()
|
|
|
|
XCTAssertEqual(helper.tracking.history, [.subscription(expectedSubscription)])
|
|
XCTAssertEqual(helper.subscription.history, [.cancelled])
|
|
|
|
XCTAssertEqual(helper.publisher.send(42), .none)
|
|
helper.publisher.send(completion: .finished)
|
|
helper.publisher.send(completion: .finished)
|
|
|
|
XCTAssertEqual(helper.tracking.history, [.subscription(expectedSubscription)])
|
|
XCTAssertEqual(helper.subscription.history, [.cancelled])
|
|
}
|
|
|
|
private static func testCompletionReleasesUpstreamSubscription<Operator: Publisher>(
|
|
expectedSubscription: StringSubscription,
|
|
_ makeOperator: (CustomPublisher) -> Operator
|
|
) throws where Operator.Output: Equatable {
|
|
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
|
|
initialDemand: nil,
|
|
receiveValueDemand: .none,
|
|
createSut: makeOperator)
|
|
|
|
XCTAssertEqual(helper.tracking.history, [.subscription(expectedSubscription)])
|
|
XCTAssertEqual(helper.subscription.history, [])
|
|
|
|
helper.publisher.send(completion: .finished)
|
|
helper.publisher.send(completion: .finished)
|
|
|
|
XCTAssertEqual(helper.tracking.history, [.subscription(expectedSubscription),
|
|
.completion(.finished)])
|
|
XCTAssertEqual(helper.subscription.history, [])
|
|
|
|
try XCTUnwrap(helper.downstreamSubscription).request(.max(1))
|
|
try XCTUnwrap(helper.downstreamSubscription).request(.max(1))
|
|
|
|
XCTAssertEqual(helper.tracking.history, [.subscription(expectedSubscription),
|
|
.completion(.finished)])
|
|
XCTAssertEqual(helper.subscription.history, [])
|
|
}
|
|
}
|
|
|
|
private let testValue = ["test": "TestDecodable"]
|