CxTest

A collection of test helpers for Combine

Build Status Current Version swift-package-manager platforms swift-version license

Testing Combine can be cumbersome. If you need to test any logic to Publishers you create or expose, one of the only options is to throw logic into a Sink:

func testPublisherFailsWithSpecifiedError() {
  let failing = Fail(outputType: Void.self, failure: SomeError())
    .sink(receiveCompletion: { completion in 
      switch completion {
        case .finished:
          XCTFail("Did not fail with an error")
        case .failure:
          return
      }
    }, receiveValue: { output in
      XCTFail("Should not have received value: \(output)")
    })
}

With CxTest, the previous example is simplified to this:

let failing = Fail(outputType: Void.self, failure: SomeError())
    .assertFailure()

When you’re creating your own Combine publishers or operators, testing can normally be done using something like a PassthroughSubject to avoid having to actually call an asynchrounous API:

func testValuesAreLessThan10() {
  let passthrough = PassthroughSubject<Int, Never>()

  let clamped = passthrough
      .clamp(range: Int.min..<10)
      .assert(lessThan: 10)

  passthrough.send(20)
  passthrough.send(40)
  passthrough.send(completion: .finished)
}

CxTest also has full support for asynchronous APIs:

func testValuesAreLessThan10Async() {
  let clampedExpectation = XCTestExpectation(description: "clamped")

  let clamped = AsyncNumberPublisher()
      .clamp(range: Int.min..<10)
      .assert(lessThan: 10, expectation: clampedExpectation)

  wait(for: [clampedExpectation], timeout: 5)
}

CxTest has a comparable operator for every XCTest assertion with the exception of XCTAssertThrowsError and XCTAssertNoThrow. These error throwing assertions are replaced by assertFailure(), assertError(type:expectation:), and assertError(equals:expectation:) operators.