Subject's subscription cancelled before values are sent downstream

I've got a publisher that subscribes to a PassthroughSubject and forwards the subject's values downstream. However, when I subscribe to the publisher, I see that the subscription is cancelled before I have a chance to push values through via the subject.

I've distilled the issue into the following minimal test case.

import XCTest
import Combine

@available(OSX 10.15, *)
public struct PassthroughPublisher: Publisher {
  public typealias Output = String
  public typealias Failure = Error
  
  let subject: PassthroughSubject<String, Error>

  init(subject: PassthroughSubject<String, Error>) {
    self.subject = subject
  }
  
  public func receive<S>(subscriber: S)
      where S : Subscriber, PassthroughPublisher.Failure == S.Failure, PassthroughPublisher.Output == S.Input
  {
    subject.subscribe(subscriber)
  }
}

class MinimalTestCase: XCTestCase {

  func testPassthroughPublisher() {
    let promise = expectation(description: "Call completes successfully")
    let subject = PassthroughSubject<String, Error>()
    let publisher = PassthroughPublisher(subject: subject)
    
    _ = publisher
      .print()
      .sink(
        receiveCompletion: { completion in
          switch completion {
          case .finished:
            promise.fulfill()
          case .failure(let error):
            XCTFail("Unnexpected error " + error.localizedDescription)
          }
        },
        receiveValue: { value in
          XCTAssert(value == "a")
        }
      )
    
    subject.send("a")
    wait(for: [promise], timeout: 5)
  }
}

When I run the test, I see that the subscription is cancelled before values are sent via the subject.

Test Suite 'MinimalTestCase' started at 2019-07-21 09:47:43.534
Test Case '-[MinimalTestCase testPassthroughPublisher]' started.
receive subscription: (PassthroughSubject)
request unlimited
receive cancel
/[...]/Workspace.swift:49: error: -[MinimalTestCase testPassthroughPublisher] : Asynchronous wait failed: Exceeded timeout of 5 seconds, with unfulfilled expectations: "Call completes successfully".

What am I missing? Where am I going wrong?

I'm on Xcode version 11.0 beta 4 (11M374r).

You need to capture the Cancellable value returned from the sink, otherwise it will cancel the subscription when it’s deinited.

3 Likes

Aha! Thanks, that was it.

You should also know that assigning an expression to an underscore merely suppresses warnings; it has no other effect on your code.