Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
2 contributors

Users who have contributed to this file

@heckj @nanujogi
314 lines (270 sloc) 13.3 KB
//
// MergingPipelineTests.swift
// UsingCombineTests
//
// Created by Joseph Heck on 7/19/19.
// Copyright © 2019 SwiftUI-Notes. All rights reserved.
//
import Combine
import Entwine // to get access to Signal as a public enum
import EntwineTest
// library loaded from https://github.com/tcldr/Entwine/blob/master/Assets/EntwineTest/README.md
// as a swift package https://github.com/tcldr/Entwine.git : 0.6.0, Next Major Version
import XCTest
class MergingPipelineTests: XCTestCase {
// since I'm screwed on using the built in equatable with a tuple response type from the operator I'm testing
// we'll make a one-off checking function to validate the expected virtualtime and resulting values all match up.
// Global function 'XCTAssertEqual(_:_:_:file:line:)' requires that '(VirtualTime, Signal<(String, Int), Never>)' conform to 'Equatable'
func testSequenceMatch<T0, T1, F0>(sequenceItem: (VirtualTime, Signal<(T0, T1), F0>),
time: VirtualTime,
inputvalues: (T0, T1)) -> Bool {
if sequenceItem.0 != time {
return false
}
if sequenceItem.1.debugDescription != Signal<(T0, T1), F0>.input(inputvalues).debugDescription {
return false
}
return true
}
func testCombineLatest() {
// setup
let testScheduler = TestScheduler(initialClock: 0)
// set up the inputs and timing
let testablePublisher1: TestablePublisher<String, Never> = testScheduler.createRelativeTestablePublisher([
(100, .input("a")),
(200, .input("b")),
(350, .input("c")),
])
let testablePublisher2: TestablePublisher<Int, Never> = testScheduler.createRelativeTestablePublisher([
(100, .input(1)),
(250, .input(2)),
(300, .input(3)),
])
let merged = Publishers.CombineLatest(testablePublisher1, testablePublisher2)
// validate
// run the virtual time scheduler
let testableSubscriber = testScheduler.start { return merged }
// check the collected results
XCTAssertEqual(testableSubscriber.recordedOutput.count, 6)
// print(testableSubscriber.recordedOutput)
// TestSequence<(String, Int), Never>(contents: [(200, .subscribe), (300, .input(("a", 1))), (400, .input(("b", 1))), (450, .input(("b", 2))), (500, .input(("b", 3))), (550, .input(("c", 3)))])
let firstInSequence = testableSubscriber.recordedOutput[0]
XCTAssertEqual(firstInSequence.0, 200) // checks the virtualtime of the subscription
// which is always expected to be 200 with this scheduled
// filter the output signals down to just the inputs - drop any subscriptions, cancel, or completions
let outputSignals = testableSubscriber.recordedOutput.filter { time, signal -> Bool in
// input type is (VirtualTime, Signal<(String, Int), Never>)
switch signal {
case .input(_, _):
return true
default:
return false
}
}
XCTAssertEqual(outputSignals.count, 5)
// print(outputSignals)
// TestSequence<(String, Int), Never>(contents: [(300, .input(("a", 1))), (400, .input(("b", 1))), (450, .input(("b", 2))), (500, .input(("b", 3))), (550, .input(("c", 3)))])
// NOTE(heckj) - well this is an ugly hack, but it works
// normally an Entwine Signal would be equatable because the enumeration includes equatable conformance.
// However, that equatable conformance relies on the underlying type being passed around to be equatable.
// When we're working with one of this operators that merge streams, the output type in question is a
// tuple, which can not provide equatable conformance.
// Swift tuples aren't allowed to conform to protocols, which means they can't ever be equatable.
// the hack that I'm using to get around this conformance ick is utilizing debugDescription to create a
// string from the Signal reference, and then comparing that to a Signal instance created as the expected
// value.
let _ = outputSignals[0] // a tuple instance of (VirtualTime, Signal<(String, Int)>)
let _ = outputSignals[0].1 // the signal itself: type Signal<(String, Int)>)
let foo = outputSignals[0].1.debugDescription // converts the signal into a string using debugDescription
let expected = Signal<(String, Int), Never>.input(("a", 1)).debugDescription
XCTAssertEqual(foo, expected)
XCTAssertTrue(
testSequenceMatch(sequenceItem: outputSignals[0], time: 300, inputvalues: ("a", 1))
)
XCTAssertTrue(
testSequenceMatch(sequenceItem: outputSignals[1], time: 400, inputvalues: ("b", 1))
)
XCTAssertTrue(
testSequenceMatch(sequenceItem: outputSignals[2], time: 450, inputvalues: ("b", 2))
)
XCTAssertTrue(
testSequenceMatch(sequenceItem: outputSignals[3], time: 500, inputvalues: ("b", 3))
)
XCTAssertTrue(
testSequenceMatch(sequenceItem: outputSignals[4], time: 550, inputvalues: ("c", 3))
)
// In hindsight, I don't know that I really care about the timing of all these results, aside
// from the fact that it makes for an illuminating record of how combineLatest() itself
// functions.
}
func testCombineLatestWithFailure() {
// setup
enum testFailureCondition: Error {
case example
}
let testScheduler = TestScheduler(initialClock: 0)
// set up the inputs and timing
let testablePublisher1: TestablePublisher<String, Error> = testScheduler.createRelativeTestablePublisher([
(100, .input("a")),
(200, .input("b")),
(350, .input("c")),
(400, .completion(.failure(testFailureCondition.example)))
])
let testablePublisher2: TestablePublisher<Int, Error> = testScheduler.createRelativeTestablePublisher([
(100, .input(1)),
(250, .input(2)),
(300, .input(3)),
(450, .input(4)),
])
let merged = Publishers.CombineLatest(testablePublisher1, testablePublisher2)
// validate
// run the virtual time scheduler
let testableSubscriber = testScheduler.start { return merged }
// check the collected results
XCTAssertEqual(testableSubscriber.recordedOutput.count, 7)
print(testableSubscriber.recordedOutput)
// TestSequence<(String, Int), Error>(contents:
// [(200, .subscribe),
// (300, .input(("a", 1))),
// (400, .input(("b", 1))),
// (450, .input(("b", 2))),
// (500, .input(("b", 3))),
// (550, .input(("c", 3))),
// (600, .completion(failure(UsingCombineTests.MergingPipelineTests.(unknown context at $108734d68).(unknown context at $108734dd8).testFailureCondition.example)))
// terminating one of the streams completing with .failure terminates all of the pipeline
// ])
// verify the virtual time of the subscription signal
XCTAssertEqual(testableSubscriber.recordedOutput[0].0, 200)
// verify that the first signal was a subscription
XCTAssertEqual(testableSubscriber.recordedOutput[0].1.debugDescription, ".subscribe")
XCTAssertTrue(
testSequenceMatch(sequenceItem: testableSubscriber.recordedOutput[1], time: 300, inputvalues: ("a", 1))
)
XCTAssertTrue(
testSequenceMatch(sequenceItem: testableSubscriber.recordedOutput[2], time: 400, inputvalues: ("b", 1))
)
XCTAssertTrue(
testSequenceMatch(sequenceItem: testableSubscriber.recordedOutput[3], time: 450, inputvalues: ("b", 2))
)
XCTAssertTrue(
testSequenceMatch(sequenceItem: testableSubscriber.recordedOutput[4], time: 500, inputvalues: ("b", 3))
)
XCTAssertTrue(
testSequenceMatch(sequenceItem: testableSubscriber.recordedOutput[5], time: 550, inputvalues: ("c", 3))
)
// verify the virtual time of the completion signal
XCTAssertEqual(testableSubscriber.recordedOutput[6].0, 600)
// verify that the final signal was a completion
XCTAssertTrue(testableSubscriber.recordedOutput[6].1.isCompletion)
}
func testCombineLatest3() {
// setup
let testScheduler = TestScheduler(initialClock: 0)
// set up the inputs and timing
let testablePublisher1: TestablePublisher<String, Never> = testScheduler.createRelativeTestablePublisher([
(100, .input("a")),
(200, .input("b")),
(350, .input("c")),
(400, .completion(.finished))
])
let testablePublisher2: TestablePublisher<Int, Never> = testScheduler.createRelativeTestablePublisher([
(100, .input(1)),
(250, .input(2)),
(300, .input(3)),
(450, .input(4)),
])
let testablePublisher3: TestablePublisher<String, Never> = testScheduler.createRelativeTestablePublisher([
(100, .input("x")),
(200, .input("y")),
(350, .input("z")),
])
let mergedPipeline = Publishers.CombineLatest3(testablePublisher1, testablePublisher2, testablePublisher3)
// validate
// run the virtual time scheduler
let testableSubscriber = testScheduler.start { return mergedPipeline }
let expected: TestSequence<(String, Int, String), Never> = [
(200, .subscription),
(300, .input(("a", 1, "x"))),
(400, .input(("b", 1, "x"))),
(400, .input(("b", 1, "y"))),
(450, .input(("b", 2, "y"))),
(500, .input(("b", 3, "y"))),
(550, .input(("c", 3, "y"))),
(550, .input(("c", 3, "z"))),
// pipeline 1 terminated at 600 - notice that we don't see an overall termination here due to that
(650, .input(("c", 4, "z"))),
]
// using the latest hotness of Entwine - post 0.6.0 release (part of master branch, as of 20 July 2019)
// mapInput does the transformation from tuple to struct, with the struct's defined in
// InterimTestingStructs.swift
let mappedExpected = expected.mapInput(Tuple3.init)
let mappedResults = testableSubscriber.recordedOutput.mapInput(Tuple3.init)
XCTAssertEqual(mappedResults, mappedExpected)
}
func testZip() {
// setup
let testScheduler = TestScheduler(initialClock: 0)
// set up the inputs and timing
let testablePublisher1: TestablePublisher<String, Never> = testScheduler.createRelativeTestablePublisher([
(100, .input("a")),
(200, .input("b")),
(350, .input("c")),
(400, .completion(.finished))
])
let testablePublisher2: TestablePublisher<Int, Never> = testScheduler.createRelativeTestablePublisher([
(100, .input(1)),
(250, .input(2)),
(300, .input(3)),
(450, .input(4)),
])
let mergedPipeline = Publishers.Zip(testablePublisher1, testablePublisher2)
// validate
// run the virtual time scheduler
let testableSubscriber = testScheduler.start { return mergedPipeline }
print(testableSubscriber.recordedOutput)
let expected: TestSequence<(String, Int), Never> = [
(200, .subscription),
(300, .input(("a", 1))),
(450, .input(("b", 2))),
(550, .input(("c", 3))),
(600, .completion(.finished))
]
// using the latest hotness of Entwine - post 0.6.0 release (part of master branch, as of 20 July 2019)
// mapInput does the transformation from tuple to struct, with the struct's defined in
// InterimTestingStructs.swift
let mappedExpected = expected.mapInput(Tuple2.init)
let mappedResults = testableSubscriber.recordedOutput.mapInput(Tuple2.init)
XCTAssertEqual(mappedResults, mappedExpected)
}
func testMerge() {
// setup
let testScheduler = TestScheduler(initialClock: 0)
// set up the inputs and timing
let testablePublisher1: TestablePublisher<String, Never> = testScheduler.createRelativeTestablePublisher([
(100, .input("a")),
(200, .input("b")),
(350, .input("c")),
(400, .completion(.finished))
])
let testablePublisher2: TestablePublisher<String, Never> = testScheduler.createRelativeTestablePublisher([
(100, .input("x")),
(200, .input("y")),
(300, .input("z")),
])
let mergedPipeline = Publishers.Merge(testablePublisher1, testablePublisher2)
// validate
// run the virtual time scheduler
let testableSubscriber = testScheduler.start { return mergedPipeline }
// print(testableSubscriber.recordedOutput)
let expected: TestSequence<(String), Never> = [
(200, .subscription),
(300, .input("a")),
(300, .input("x")),
(400, .input("b")),
(400, .input("y")),
(500, .input("z")),
(550, .input("c")),
]
XCTAssertEqual(testableSubscriber.recordedOutput, expected)
}
}
You can’t perform that action at this time.