Skip to content

Commit 0a868c4

Browse files
authored
Merge pull request #2 from SwiftRex/CombineExtensions
Testing Extensions for Combine
2 parents d3eca68 + 3c33649 commit 0a868c4

File tree

1 file changed

+149
-0
lines changed

1 file changed

+149
-0
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
//
2+
// CombineTesting.swift
3+
// TestingExtensions
4+
//
5+
// Created by Luiz Rodrigo Martins Barbosa on 23.03.21.
6+
// Copyright © 2021 Lautsprecher Teufel GmbH. All rights reserved.
7+
//
8+
9+
import Combine
10+
import XCTest
11+
12+
extension XCTestCase {
13+
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
14+
public func assert<P: Publisher>(
15+
publisher: P,
16+
eventuallyReceives values: P.Output...,
17+
andCompletes: Bool = false,
18+
timeout: TimeInterval
19+
) -> () -> Void where P.Output: Equatable {
20+
var collectedValues: [P.Output] = []
21+
let valuesExpectation = expectation(description: "Expected values")
22+
let completionExpectation = expectation(description: "Expected completion")
23+
valuesExpectation.expectedFulfillmentCount = values.count
24+
if !andCompletes { completionExpectation.fulfill() }
25+
let cancellable = publisher.sink(
26+
receiveCompletion: { result in
27+
switch result {
28+
case .finished:
29+
if andCompletes { completionExpectation.fulfill() }
30+
case let .failure(error):
31+
XCTFail("Received failure: \(error)")
32+
}
33+
},
34+
receiveValue: { value in
35+
collectedValues.append(value)
36+
valuesExpectation.fulfill()
37+
}
38+
)
39+
40+
return { [weak self] in
41+
guard let self = self else {
42+
XCTFail("Test ended before waiting for expectations")
43+
return
44+
}
45+
self.wait(for: [valuesExpectation, completionExpectation], timeout: timeout)
46+
XCTAssertEqual(collectedValues, values, "Values don't match:\nreceived:\n\(collectedValues)\n\nexpected:\n\(values)")
47+
_ = cancellable
48+
}
49+
}
50+
51+
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
52+
public func assert<P: Publisher>(
53+
publisher: P,
54+
eventuallyReceives values: P.Output...,
55+
andCompletesWith: ValidateCompletion<P.Failure>,
56+
timeout: TimeInterval
57+
) -> () -> Void where P.Output: Equatable {
58+
var collectedValues: [P.Output] = []
59+
let valuesExpectation = expectation(description: "Expected values")
60+
let completionExpectation = expectation(description: "Expected completion")
61+
valuesExpectation.expectedFulfillmentCount = values.count
62+
let cancellable = publisher.sink(
63+
receiveCompletion: { result in
64+
XCTAssertTrue(andCompletesWith.isExpected(result), "Unexpected completion: \(result)")
65+
completionExpectation.fulfill()
66+
},
67+
receiveValue: { value in
68+
collectedValues.append(value)
69+
valuesExpectation.fulfill()
70+
}
71+
)
72+
73+
return { [weak self] in
74+
guard let self = self else {
75+
XCTFail("Test ended before waiting for expectations")
76+
return
77+
}
78+
self.wait(for: [valuesExpectation, completionExpectation], timeout: timeout)
79+
XCTAssertEqual(collectedValues, values, "Values don't match:\nreceived:\n\(collectedValues)\n\nexpected:\n\(values)")
80+
_ = cancellable
81+
}
82+
}
83+
84+
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
85+
public func assert<P: Publisher>(
86+
publisher: P,
87+
completesWithoutValues: ValidateCompletion<P.Failure>,
88+
timeout: TimeInterval
89+
) -> () -> Void {
90+
let completionExpectation = expectation(description: "Expected completion")
91+
let cancellable = publisher.sink(
92+
receiveCompletion: { result in
93+
XCTAssertTrue(completesWithoutValues.isExpected(result), "Unexpected completion: \(result)")
94+
completionExpectation.fulfill()
95+
},
96+
receiveValue: { value in
97+
XCTFail("Unexpected value received: \(value)")
98+
}
99+
)
100+
101+
return { [weak self] in
102+
guard let self = self else {
103+
XCTFail("Test ended before waiting for expectations")
104+
return
105+
}
106+
self.wait(for: [completionExpectation], timeout: timeout)
107+
_ = cancellable
108+
}
109+
}
110+
}
111+
112+
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
113+
public struct ValidateCompletion<Failure: Error> {
114+
let isExpected: (Subscribers.Completion<Failure>) -> Bool
115+
public init(validate: @escaping (Subscribers.Completion<Failure>) -> Bool) {
116+
self.isExpected = validate
117+
}
118+
119+
public static var isSuccess: ValidateCompletion {
120+
ValidateCompletion { result in
121+
guard case .finished = result else { return false }
122+
return true
123+
}
124+
}
125+
126+
public static var isFailure: ValidateCompletion {
127+
ValidateCompletion { result in
128+
guard case .failure = result else { return false }
129+
return true
130+
}
131+
}
132+
133+
public static func failedWithError(_ errorPredicate: @escaping (Failure) -> Bool) -> ValidateCompletion {
134+
ValidateCompletion { result in
135+
guard case let .failure(error) = result else { return false }
136+
return errorPredicate(error)
137+
}
138+
}
139+
}
140+
141+
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
142+
extension ValidateCompletion where Failure: Equatable {
143+
public static func failedWithError(_ expectedError: Failure) -> ValidateCompletion {
144+
ValidateCompletion { result in
145+
guard case let .failure(error) = result else { return false }
146+
return error == expectedError
147+
}
148+
}
149+
}

0 commit comments

Comments
 (0)