@mattneub & @freak4pc - Thanks for verifying the fix, and thank you @mikevelu for the bug report!
Hey @Tony_Parker, sorry to be the bearer of bad news, but it seems this has regressed back in the final release of 11.4.
Here's a test showcasing this:
class FakeSuite: XCTestCase {
var subscription: AnyCancellable!
func test() {
let expect = expectation(description: "tester")
var outerCompleted = false
subscription = Just("trigger")
.map { _ -> AnyPublisher<Void, Never> in
Timer
.publish(every: 0.1, on: RunLoop.current, in: .common)
.autoconnect()
.map { _ in () }
.prefix(8)
.handleEvents(receiveOutput: { _ in print("inner value") },
receiveCompletion: { print("inner completed: \($0)") })
.eraseToAnyPublisher()
}
.switchToLatest()
.sink(
receiveCompletion: {
print("completed \($0)")
outerCompleted = true
expect.fulfill()
},
receiveValue: { _ in print("value") }
)
wait(for: [expect], timeout: 2)
XCTAssertTrue(outerCompleted)
}
}
On Xcode 11.4 beta 3 this outputs the following and passes:
inner value
value
inner value
value
inner value
value
inner value
value
inner value
value
inner value
value
inner value
value
inner value
value
inner completed: finished
completed finished
On Xcode 11.4 Final the test fails and I'm only seeing this output:
inner value
inner value
inner value
inner value
inner value
inner value
inner value
inner value
inner completed: finished
I also see a
Asynchronous wait failed: Exceeded timeout of 2 seconds, with unfulfilled expectations: "tester".
This actually seems to be in worse shape?
The inner publisher completes and emits, but I'm not seeing its events relayed outside at all.
By the way adding a .print("stl")
after the switchToLatest yields only
stl: receive subscription: (SwitchToLatest)
stl: request unlimited
Hi @freak4pc. Given your level of experience, do you have any advice about dealing with the various behaviors of Combine across system versions? For example, did you have to rewrite basic publishers as replacements for the lacking of flaky built-in ones?
It's not that I want to stress out that the framework is (obviously) still lacking a solid test suite. I'm just wondering what I'm supposed to do when I really have to ship an app that can use Combine, so that... I can plan the amount of required work.
Edit: @Tony_Parker, your advice is welcome too, actually. Sorry for not mentioning you.
Hey Gwendel!
I didn't rewrite any of the basic publishers, but I have some custom publishers of things I'm missing for what I'd consider "production use" (withLatestFrom, materialize, dematerialize, and more). You can see those here: GitHub - CombineCommunity/CombineExt: CombineExt provides a collection of operators, publishers and utilities for Combine, that are not provided by Apple themselves, but are common in other Reactive Frameworks and standards.
IRT to testing it seems there isn't a proper Test Scheduler like we have in RxSwift, yet. You can either use something like Entwine's TestScheduler or use sink
and some XCTestExpectation(s). See the test suite of CombineExt above to see some the latter option.
All right. I guess the Combine warts don't hit that often, then. Thank you
I definitely have an eye on this one ;-)
I think it's mostly stable :) Especially given how young it is.
Totally. I guess you remember the early days of RxSwift, and... it must have been sweaty ;-). I came to it long after it was already stabilized: I was lucky ;-)
Do you know about GitHub - groue/CombineExpectations: Utilities for tests that wait for Combine publishers?
It does not help testing schedulers. But it helps testing behaviors of black-box publishers:
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!"])
}
}
It's like RxBlocking, but without any RunLoop voodoo: it just uses XCTestExpectation.
Cool! Looks like a neat wrapper around XCTestExpectation :))
What version of the OS are you on? Combine ships as part of iOS/macOS, so it's that version which matters, not Xcode.
@Tony_Parker Ah, that's interesting, didn't know this.
I'm running macOS Catalina 10.15.3. Actually when running the tests in an iOS 13.4 simulator, it passes. When running the tests on macOS (e.g. "My Mac"), it fails with the output I showed you above. Very stranage.
Thanks!
Running an iOS app in the iOS 13.4 simulator makes it run with the iOS 13.4 version of Combine.
Running a macOS app on macOS 10.15.3 makes it run with the macOS 10.15.3 version of Combine. But you need macOS 10.15.4 to get the same common frameworks as iOS 13.4.
Thanks Rob, I'll update and confirm.
Would this specifically be an SDK thing? Meaning, if I build and ship from macOS 10.5.4 and run the binary on macOS 10.15.3, would I expect the right or wrong behavior in this case?
You will generally get 10.15.3 bugs if you compile on 10.15.4 but run on 10.15.3.
That's really unfortunate. This means it's as good as marking this operator @available
on 10.15.4 and up as it basically doesn't work below that version :( Thanks for your answer, regardless.
I'll confirm here after upgrading.
You could try using the switchToLatest
implementation from OpenCombine or CombineX. Either will require a little tweaking to supply lock implementations.
I have to deny the claim that there's a regression in switchToLatest
as compared to the preceding beta. As I say here as well as in my preceding message in this thread, there is a bug before iOS 13.4, but in 13.4 it is fixed. I originally tested in the 11.4 beta, and in my testing in 11.4 final, it is still fixed.
Update Okay, I see that this point has already been made. But I don't see this as a big issue because, after all, Combine is already iOS 13 only, so I would have no compunction in releasing an app that requires iOS 13.4 or later, in order to take advantage of it.
I disagree with you on this, my angle on this is "I'm already limiting to iOS 13, so I need to further limit my supported devices just to work with Combine properly?"