Testing with Combine

Hi. I'm new in swift Testing. So i'm researching. how could i test a pineline in combine.

i'm using

    @Test func popTrigger() async throws {
        popTrigger.send()
        
        try await Task.sleep(nanoseconds: 200_000_000)
        
        #expect(navigator.isPopActionCalled == true, "Must be pop to previous view")
    }

As you can see. i'm using arbitrary sleep number time.
i'm wondering are there any API like wait expectation like the one in XCTestCase to improve time of testing.

1 Like

Hi @chau-phan94,

The pattern I typically use (to check if Combine indeed triggers the updates I expect) is:

var cancelables = Set<AnyCancellable>()
    
@Test mutating func popTrigger() throws {
    var valueToObserve = false

    popTrigger.$isPopActionCalled.sink { newValue in
            valueToObserve = newValue
        }.store(in: &cancelables)
        
    popTrigger.send()
        
    #expect(valueToObserve == true)
}

That works fine, as long as your publisher and subscription are synchronous. A single receive(on:) somewhere in the pipeline and it won't work anymore.

@chau-phan94 For proper testing without being able to wait on an expectation, you'd need to more fully constrain your dependencies in the publisher. For instance, Combine abstracts its schedulers, so you could replace any direct use of schedulers with an abstracted version that allows you control when events are emitted. For instance, those provided by the combine-schedulers library.

Another approach is connecting Combine to Swift concurrency directly. If your isPopActionCalled property is @Published, you could simply await the .values async sequence from it and grab values that way.

1 Like