Here's an easy to copy and paste reference on how to write common test assertions for Swift Combine Publisher
s in XCTest in Xcode.
- How to test
Publisher
publishes one value then finishes - How to test
Publisher
publishes one value then a failure - How to test
Publisher
publishes many values then finishes - How to test
Publisher
publishes many values then a failure - How to test
Publisher
publishes no values then finishes - How to test
Publisher
publishes no values then a failure
You can see these snippets in action and experiment with the syntax by checking out the example project.
While researching this post, I found that there is too much boilerplate required to test a Publisher
, resulting in code that is harder to read.
I started building a Swift Package to try addressing this problem: CombineTestHelpers provides handy one liners for each scenario in this cheat sheet and more.
In the snippets below you'll see some code in the form <# ... #>
.
That's the source code placeholder Xcode notation and makes it easier to adapt the code to your needs.
Xcode will let you jump to those placeholders with Tab
.
All the examples in this cheat sheet follow the same pattern:
to test a Combine Publisher
, define an XCTestExpectation
and make the test subscribe to the publisher with sink(receiveCompletion:, receiveValue)
.
Fulfill the expectation in either the value or completion closure, depending on the kind of behavior you want to verify.
If necessary, you can run one or more XCTestAssert-
assertions on the value and completion you receive.
How to test Publisher
publishes one value then finishes
let expectation = XCTestExpectation(description: "Publishes one value then finishes")
var values: [<# Publisher Output type #>] = []
publisher
.sink(
receiveCompletion: { completion in
guard case .finished = completion else { return }
expectation.fulfill()
},
receiveValue: { value in
guard values.isEmpty else {
return XCTFail("Expected to receive only one value, got another: (\(value))")
}
XCTAssertEqual(value, <# expected value #>)
values.append(value)
}
)
.store(in: &cancellables)
wait(for: [expectation], timeout: 0.5)
How to test Publisher
publishes one value then a failure
let expectation = XCTestExpectation(description: "Publishes one value then fails")
var values: [<# Publisher Output type #>] = []
publisher
.sink(
receiveCompletion: { completion in
guard case .failure(let error) = completion else { return }
XCTAssertEqual(error, <# expected error #>)
expectation.fulfill()
},
receiveValue: { value in
guard values.isEmpty else {
return XCTFail("Expected to receive only one value, got another (\(value))")
}
XCTAssertEqual(value, <# expected value #>)
values.append(value)
}
)
.store(in: &cancellables)
wait(for: [expectation], timeout: 0.5)
How to test Publisher
publishes many values then finishes
let expectation = XCTestExpectation(description: "Publishes values then finishes")
var values: [<# Publisher Output type #>] = []
publisher
.sink(
receiveCompletion: { completion in
guard case .finished = completion else { return }
expectation.fulfill()
},
receiveValue: {
values.append($0)
}
)
.store(in: &cancellables)
wait(for: [expectation], timeout: 0.5)
XCTAssertEqual(values, <# expected values #>)
How to test Publisher
publishes many values then a failure
let expectation = XCTestExpectation(description: "Publishes many values then a failure")
var values: [<# Publisher Output type #>] = []
publisher
.sink(
receiveCompletion: { completion in
guard case .failure(let error) = completion else { return }
XCTAssertEqual(error, <# expected error #>)
expectation.fulfill()
},
receiveValue: {
values.append($0)
}
)
.store(in: &cancellables)
wait(for: [expectation], timeout: 0.5)
XCTAssertEqual(values, <# expected values #>)
How to test Publisher
publishes no values then finishes
let expectation = XCTestExpectation(description: "Finishes without publishing values")
publisher
.sink(
receiveCompletion: { completion in
guard case .finished = completion else { return }
expectation.fulfill()
},
receiveValue: {
XCTFail("Expected to finish without receiving any value, got \($0)")
}
)
.store(in: &cancellables)
wait(for: [expectation], timeout: 0.5)
How to test Publisher
publishes no values then a failure
let expectation = XCTestExpectation(description: "Fails without publishing values")
publisher
.sink(
receiveCompletion: { completion in
guard case .failure(let error) = completion else { return }
XCTAssertEqual(error, <# expected error #>)
expectation.fulfill()
},
receiveValue: {
XCTFail("Expected to fail without receiving any value, got \($0)")
}
)
.store(in: &cancellables)
wait(for: [expectation], timeout: 0.5)
You'll find more insights on how to write tests for code using Combine in my book Test-Driven Development in Swift with SwiftUI and Combine.
What are the most common Publisher
behavior you test?
I'd love to hear from you!
Leave a comment below or get in touch on Twitter at @mokagio.
OpenGraph Image by Sharon Pittaway on Unsplash