Files
Sergej Jaskiewicz 1bb3583a36 More tests for contract violations in operators
- 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
2019-11-19 18:38:01 +03:00

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"]