Combine Expectations
用于等待 Combine 发布者的测试工具。
最新发布版本: 版本 0.7.0 (2021 年 1 月 9 日) • 发行说明
要求: iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ • Swift 5.1+ / Xcode 11.0+
联系方式: 在 GitHub 问题 中报告错误并提问。
使用 XCTestExpectation 测试 Combine 发布者通常需要一个大量的样板代码。
CombineExpectations 致力于简化这些测试。它定义了一个等待 发布者预期 的 XCTestCase 方法。
- 用法
- 安装
- 发布者预期: availableElements、completion、elements、finished、last、next()、next(count)、prefix(maxLength)、recording、single
用法
等待 发布者预期 可以使您的测试看起来像这样
import XCTest
import CombineExpectations
class PublisherTests: XCTestCase {
func testElements() throws {
// 1. Create a publisher
let publisher = ...
// 2. Start recording the publisher
let recorder = publisher.record()
// 3. Wait for a publisher expectation
let elements = try wait(for: recorder.elements, timeout: ..., description: "Elements")
// 4. Test the result of the expectation
XCTAssertEqual(elements, ["Hello", "World!"])
}
}
当您等待发布者预期时
- 如果预期在指定的超时时间内未得到满足,则测试失败。
- 如果无法返回预期的值,则会抛出错误。例如,等待
recorder.elements
如果发布者因错误而完成,则会抛出发布者错误。 - 如果期望已经达到等待的状态,则
wait
方法会立即返回。
可以为发布者多次等待。
class PublisherTests: XCTestCase {
func testPublisher() throws {
let publisher = ...
let recorder = publisher.record()
// Wait for first element
_ = try wait(for: recorder.next(), timeout: ...)
// Wait for second element
_ = try wait(for: recorder.next(), timeout: ...)
// Wait for successful completion
try wait(for: recorder.finished, timeout: ...)
}
}
并非所有测试都需要等待,因为某些发布者的期望可以立即满足。在这种情况下,最好是使用同步的 get()
方法,而不是使用 wait(for:timeout:)
,如下所示:
class PublisherTests: XCTestCase {
func testSynchronousPublisher() throws {
// 1. Create a publisher
let publisher = ...
// 2. Start recording the publisher
let recorder = publisher.record()
// 3. Grab the expected result
let elements = try recorder.elements.get()
// 4. Test the result of the expectation
XCTAssertEqual(elements, ["Hello", "World!"])
}
}
与 wait(for:timeout:)
类似,get()
方法可以多次调用。
class PublisherTests: XCTestCase {
// SUCCESS: no error
func testPassthroughSubjectSynchronouslyPublishesElements() throws {
let publisher = PassthroughSubject<String, Never>()
let recorder = publisher.record()
publisher.send("foo")
try XCTAssertEqual(recorder.next().get(), "foo")
publisher.send("bar")
try XCTAssertEqual(recorder.next().get(), "bar")
}
}
安装
在你的 Swift 包管理器 测试目标中添加对 CombineExpectations 的依赖。
import PackageDescription
let package = Package(
dependencies: [
+ .package(url: "https://github.com/groue/CombineExpectations.git", ...)
],
targets: [
.testTarget(
dependencies: [
+ "CombineExpectations"
])
]
)
发布者期望
有各种发布者期望。每一个都等待特定的发布者方面。
- availableElements:直到超时到期之前发布的所有元素
- completion:发布者的完成
- elements:直到成功完成之前发布的所有元素
- finished:发布者成功的完成
- last:最后一个发布元素
- next():下一个发布的元素
- next(count):下一个 N 个发布的元素
- prefix(maxLength):前 N 个发布的元素
- recording:整个发布者事件的记录
- single:唯一发布的元素
availableElements
recorder.availableElements
等待期望超时或已记录的发布者完成。
与其它期望不同,`availableElements` 在超时到期时不会导致测试失败。它只返回到目前为止发布的元素。
示例
// SUCCESS: no timeout, no error
func testTimerPublishesIncreasingDates() throws {
let publisher = Timer.publish(every: 0.01, on: .main, in: .common).autoconnect()
let recorder = publisher.record()
let dates = try wait(for: recorder.availableElements, timeout: ...)
XCTAssertEqual(dates.sorted(), dates)
}
completion
recorder.completion
等待已记录的发布者完成。
RecordingError.notCompleted
。
Subscribers.Completion
。
示例
// SUCCESS: no timeout, no error
func testArrayPublisherCompletesWithSuccess() throws {
let publisher = ["foo", "bar", "baz"].publisher
let recorder = publisher.record()
let completion = try wait(for: recorder.completion, timeout: ...)
if case let .failure(error) = completion {
XCTFail("Unexpected error \(error)")
}
}
// SUCCESS: no error
func testArrayPublisherSynchronouslyCompletesWithSuccess() throws {
let publisher = ["foo", "bar", "baz"].publisher
let recorder = publisher.record()
let completion = try recorder.completion.get()
if case let .failure(error) = completion {
XCTFail("Unexpected error \(error)")
}
}
失败测试的示例
// FAIL: Asynchronous wait failed
// FAIL: Caught error RecordingError.notCompleted
func testCompletionTimeout() throws {
let publisher = PassthroughSubject<String, Never>()
let recorder = publisher.record()
let completion = try wait(for: recorder.completion, timeout: ...)
}
elements
recorder.elements
等待已记录的发布者完成。
RecordingError.notCompleted
,并在发布者失败时抛出发布者错误。
示例
// SUCCESS: no timeout, no error
func testArrayPublisherPublishesArrayElements() throws {
let publisher = ["foo", "bar", "baz"].publisher
let recorder = publisher.record()
let elements = try wait(for: recorder.elements, timeout: ...)
XCTAssertEqual(elements, ["foo", "bar", "baz"])
}
// SUCCESS: no error
func testArrayPublisherSynchronouslyPublishesArrayElements() throws {
let publisher = ["foo", "bar", "baz"].publisher
let recorder = publisher.record()
let elements = try recorder.elements.get()
XCTAssertEqual(elements, ["foo", "bar", "baz"])
}
失败测试的示例
// FAIL: Asynchronous wait failed
// FAIL: Caught error RecordingError.notCompleted
func testElementsTimeout() throws {
let publisher = PassthroughSubject<String, Never>()
let recorder = publisher.record()
let elements = try wait(for: recorder.elements, timeout: ...)
}
// FAIL: Caught error MyError
func testElementsError() throws {
let publisher = PassthroughSubject<String, MyError>()
let recorder = publisher.record()
publisher.send(completion: .failure(MyError()))
let elements = try wait(for: recorder.elements, timeout: ...)
}
finished
recorder.finished
等待已记录的发布者完成。
示例
// SUCCESS: no timeout, no error
func testArrayPublisherFinishesWithoutError() throws {
let publisher = ["foo", "bar", "baz"].publisher
let recorder = publisher.record()
try wait(for: recorder.finished, timeout: ...)
}
// SUCCESS: no error
func testArrayPublisherSynchronouslyFinishesWithoutError() throws {
let publisher = ["foo", "bar", "baz"].publisher
let recorder = publisher.record()
try recorder.finished.get()
}
失败测试的示例
// FAIL: Asynchronous wait failed
func testFinishedTimeout() throws {
let publisher = PassthroughSubject<String, Never>()
let recorder = publisher.record()
try wait(for: recorder.finished, timeout: ...)
}
// FAIL: Caught error MyError
func testFinishedError() throws {
let publisher = PassthroughSubject<String, MyError>()
let recorder = publisher.record()
publisher.send(completion: .failure(MyError()))
try wait(for: recorder.finished, timeout: ...)
}
recorder.finished
可逆
// SUCCESS: no timeout, no error
func testPassthroughSubjectDoesNotFinish() throws {
let publisher = PassthroughSubject<String, Never>()
let recorder = publisher.record()
try wait(for: recorder.finished.inverted, timeout: ...)
}
失败测试的示例
// FAIL: Fulfilled inverted expectation
// FAIL: Caught error MyError
func testInvertedFinishedError() throws {
let publisher = PassthroughSubject<String, MyError>()
let recorder = publisher.record()
publisher.send(completion: .failure(MyError()))
try wait(for: recorder.finished.inverted, timeout: ...)
}
last
recorder.last
等待已记录的发布者完成。
RecordingError.notCompleted
,并在发布者失败时抛出发布者错误。
示例
// SUCCESS: no timeout, no error
func testArrayPublisherPublishesLastElementLast() throws {
let publisher = ["foo", "bar", "baz"].publisher
let recorder = publisher.record()
if let element = try wait(for: recorder.last, timeout: ...) {
XCTAssertEqual(element, "baz")
} else {
XCTFail("Expected one element")
}
}
// SUCCESS: no error
func testArrayPublisherSynchronouslyPublishesLastElementLast() throws {
let publisher = ["foo", "bar", "baz"].publisher
let recorder = publisher.record()
if let element = try recorder.last.get() {
XCTAssertEqual(element, "baz")
} else {
XCTFail("Expected one element")
}
}
失败测试的示例
// FAIL: Asynchronous wait failed
// FAIL: Caught error RecordingError.notCompleted
func testLastTimeout() throws {
let publisher = PassthroughSubject<String, Never>()
let recorder = publisher.record()
let element = try wait(for: recorder.last, timeout: ...)
}
// FAIL: Caught error MyError
func testLastError() throws {
let publisher = PassthroughSubject<String, MyError>()
let recorder = publisher.record()
publisher.send(completion: .failure(MyError()))
let element = try wait(for: recorder.last, timeout: ...)
}
next()
recorder.next()
等待已记录的发布者发出一个元素,或者完成。
RecordingError.notEnoughElements
。如果发布者在发布下一个元素之前失败,则会抛出发布者错误。
示例
// SUCCESS: no timeout, no error
func testArrayOfTwoElementsPublishesElementsInOrder() throws {
let publisher = ["foo", "bar"].publisher
let recorder = publisher.record()
var element = try wait(for: recorder.next(), timeout: ...)
XCTAssertEqual(element, "foo")
element = try wait(for: recorder.next(), timeout: ...)
XCTAssertEqual(element, "bar")
}
// SUCCESS: no error
func testArrayOfTwoElementsSynchronouslyPublishesElementsInOrder() throws {
let publisher = ["foo", "bar"].publisher
let recorder = publisher.record()
var element = try recorder.next().get()
XCTAssertEqual(element, "foo")
element = try recorder.next().get()
XCTAssertEqual(element, "bar")
}
失败测试的示例
// FAIL: Asynchronous wait failed
// FAIL: Caught error RecordingError.notEnoughElements
func testNextTimeout() throws {
let publisher = PassthroughSubject<String, Never>()
let recorder = publisher.record()
let element = try wait(for: recorder.next(), timeout: ...)
}
// FAIL: Caught error MyError
func testNextError() throws {
let publisher = PassthroughSubject<String, MyError>()
let recorder = publisher.record()
publisher.send(completion: .failure(MyError()))
let element = try wait(for: recorder.next(), timeout: ...)
}
// FAIL: Caught error RecordingError.notEnoughElements
func testNextNotEnoughElementsError() throws {
let publisher = PassthroughSubject<String, Never>()
let recorder = publisher.record()
publisher.send(completion: .finished)
let element = try wait(for: recorder.next(), timeout: ...)
}
recorder.next()
可以反转。
// SUCCESS: no timeout, no error
func testPassthroughSubjectDoesNotPublishAnyElement() throws {
let publisher = PassthroughSubject<String, Never>()
let recorder = publisher.record()
try wait(for: recorder.next().inverted, timeout: ...)
}
失败测试的示例
// FAIL: Fulfilled inverted expectation
func testInvertedNextTooEarly() throws {
let publisher = PassthroughSubject<String, Never>()
let recorder = publisher.record()
publisher.send("foo")
try wait(for: recorder.next().inverted, timeout: ...)
}
// FAIL: Fulfilled inverted expectation
// FAIL: Caught error MyError
func testInvertedNextError() throws {
let publisher = PassthroughSubject<String, MyError>()
let recorder = publisher.record()
publisher.send(completion: .failure(MyError()))
try wait(for: recorder.next().inverted, timeout: ...)
}
next(count)
recorder.next(count)
会等待记录的发布者发布 count
个元素或完成。
count
个元素,则抛出 RecordingError.notEnoughElements
。如果发布者在发布下一个 count
个元素之前失败,则会抛出发布者错误。
count
个元素的数组。
示例
// SUCCESS: no timeout, no error
func testArrayOfThreeElementsPublishesTwoThenOneElement() throws {
let publisher = ["foo", "bar", "baz"].publisher
let recorder = publisher.record()
var elements = try wait(for: recorder.next(2), timeout: ...)
XCTAssertEqual(elements, ["foo", "bar"])
elements = try wait(for: recorder.next(1), timeout: ...)
XCTAssertEqual(elements, ["baz"])
}
// SUCCESS: no error
func testArrayOfThreeElementsSynchronouslyPublishesTwoThenOneElement() throws {
let publisher = ["foo", "bar", "baz"].publisher
let recorder = publisher.record()
var elements = try recorder.next(2).get()
XCTAssertEqual(elements, ["foo", "bar"])
elements = try recorder.next(1).get()
XCTAssertEqual(elements, ["baz"])
}
失败测试的示例
// FAIL: Asynchronous wait failed
// FAIL: Caught error RecordingError.notEnoughElements
func testNextCountTimeout() throws {
let publisher = PassthroughSubject<String, Never>()
let recorder = publisher.record()
publisher.send("foo")
let elements = try wait(for: recorder.next(2), timeout: ...)
}
// FAIL: Caught error MyError
func testNextCountError() throws {
let publisher = PassthroughSubject<String, MyError>()
let recorder = publisher.record()
publisher.send("foo")
publisher.send(completion: .failure(MyError()))
let elements = try wait(for: recorder.next(2), timeout: ...)
}
// FAIL: Caught error RecordingError.notEnoughElements
func testNextCountNotEnoughElementsError() throws {
let publisher = PassthroughSubject<String, Never>()
let recorder = publisher.record()
publisher.send("foo")
publisher.send(completion: .finished)
let elements = try wait(for: recorder.next(2), timeout: ...)
}
prefix(maxLength)
recorder.prefix(maxLength)
会等待记录的发布者发布 maxLength
个元素或完成。
maxLength
个元素之前失败,则抛出发布者错误。
maxLength
个元素,或如果发布者提前完成,则少于 maxLength
个元素。
示例
// SUCCESS: no timeout, no error
func testArrayOfThreeElementsPublishesTwoFirstElementsWithoutError() throws {
let publisher = ["foo", "bar", "baz"].publisher
let recorder = publisher.record()
let elements = try wait(for: recorder.prefix(2), timeout: ...)
XCTAssertEqual(elements, ["foo", "bar"])
}
// SUCCESS: no error
func testArrayOfThreeElementsSynchronouslyPublishesTwoFirstElementsWithoutError() throws {
let publisher = ["foo", "bar", "baz"].publisher
let recorder = publisher.record()
let elements = try recorder.prefix(2).get()
XCTAssertEqual(elements, ["foo", "bar"])
}
失败测试的示例
// FAIL: Asynchronous wait failed
func testPrefixTimeout() throws {
let publisher = PassthroughSubject<String, Never>()
let recorder = publisher.record()
publisher.send("foo")
let elements = try wait(for: recorder.prefix(2), timeout: ...)
}
// FAIL: Caught error MyError
func testPrefixError() throws {
let publisher = PassthroughSubject<String, MyError>()
let recorder = publisher.record()
publisher.send("foo")
publisher.send(completion: .failure(MyError()))
let elements = try wait(for: recorder.prefix(2), timeout: ...)
}
recorder.prefix(maxLength)
可以反转。
// SUCCESS: no timeout, no error
func testPassthroughSubjectPublishesNoMoreThanSentValues() throws {
let publisher = PassthroughSubject<String, Never>()
let recorder = publisher.record()
publisher.send("foo")
publisher.send("bar")
let elements = try wait(for: recorder.prefix(3).inverted, timeout: ...)
XCTAssertEqual(elements, ["foo", "bar"])
}
失败测试的示例
// FAIL: Fulfilled inverted expectation
func testInvertedPrefixTooEarly() throws {
let publisher = PassthroughSubject<String, Never>()
let recorder = publisher.record()
publisher.send("foo")
publisher.send("bar")
publisher.send("baz")
let elements = try wait(for: recorder.prefix(3).inverted, timeout: ...)
}
// FAIL: Fulfilled inverted expectation
// FAIL: Caught error MyError
func testInvertedPrefixError() throws {
let publisher = PassthroughSubject<String, MyError>()
let recorder = publisher.record()
publisher.send("foo")
publisher.send(completion: .failure(MyError()))
let elements = try wait(for: recorder.prefix(3).inverted, timeout: ...)
}
recording
recorder.recording
会等待记录的发布者完成。
RecordingError.notCompleted
。
Record.Recording
。
示例
// SUCCESS: no timeout, no error
func testArrayPublisherRecording() throws {
let publisher = ["foo", "bar", "baz"].publisher
let recorder = publisher.record()
let recording = try wait(for: recorder.recording, timeout: ...)
XCTAssertEqual(recording.output, ["foo", "bar", "baz"])
if case let .failure(error) = recording.completion {
XCTFail("Unexpected error \(error)")
}
}
// SUCCESS: no error
func testArrayPublisherSynchronousRecording() throws {
let publisher = ["foo", "bar", "baz"].publisher
let recorder = publisher.record()
let recording = try recorder.recording.get()
XCTAssertEqual(recording.output, ["foo", "bar", "baz"])
if case let .failure(error) = recording.completion {
XCTFail("Unexpected error \(error)")
}
}
失败测试的示例
// FAIL: Asynchronous wait failed
// FAIL: Caught error RecordingError.notCompleted
func testRecordingTimeout() throws {
let publisher = PassthroughSubject<String, Never>()
let recorder = publisher.record()
let recording = try wait(for: recorder.recording, timeout: ...)
}
single
recorder.single
等待被记录的发布者完成。
RecordingError
异常。如果发布者失败,则会抛出发布者错误。
示例
// SUCCESS: no timeout, no error
func testJustPublishesExactlyOneElement() throws {
let publisher = Just("foo")
let recorder = publisher.record()
let element = try wait(for: recorder.single, timeout: ...)
XCTAssertEqual(element, "foo")
}
// SUCCESS: no error
func testJustSynchronouslyPublishesExactlyOneElement() throws {
let publisher = Just("foo")
let recorder = publisher.record()
let element = try recorder.single.get()
XCTAssertEqual(element, "foo")
}
失败测试的示例
// FAIL: Asynchronous wait failed
// FAIL: Caught error RecordingError.notCompleted
func testSingleTimeout() throws {
let publisher = PassthroughSubject<String, Never>()
let recorder = publisher.record()
let element = try wait(for: recorder.single, timeout: ...)
}
// FAIL: Caught error MyError
func testSingleError() throws {
let publisher = PassthroughSubject<String, MyError>()
let recorder = publisher.record()
publisher.send(completion: .failure(MyError()))
let element = try wait(for: recorder.single, timeout: ...)
}
// FAIL: Caught error RecordingError.tooManyElements
func testSingleTooManyElementsError() throws {
let publisher = PassthroughSubject<String, Never>()
let recorder = publisher.record()
publisher.send("foo")
publisher.send("bar")
publisher.send(completion: .finished)
let element = try wait(for: recorder.single, timeout: ...)
}
// FAIL: Caught error RecordingError.notEnoughElements
func testSingleNotEnoughElementsError() throws {
let publisher = PassthroughSubject<String, Never>()
let recorder = publisher.record()
publisher.send(completion: .finished)
let element = try wait(for: recorder.single, timeout: ...)
}