You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Combine框架单元测试中XCTestExpectation多次调用fulfill的API违规问题排查

解决方案

根据你的测试目标,分两种场景处理:

场景1:只需要验证首次发送的value1

如果你的测试仅关注handleSomeUserAction调用后立刻发出的value1,不需要管后续异步操作带来的value2,可以给流添加first()操作符,只捕获第一个事件:

func test_handleSomeUserAction_sendsValue1Immediately() {
    // given
    let expectation = expectation(description: "Receive initial value1 after user action")
    var receivedValue: ValueType?
    var cancellables = Set<AnyCancellable>()
    
    // when
    sut.someOtherStream
        .dropFirst() // 跳过CurrentValueSubject的初始默认值
        .first() // 仅取第一个发送的事件
        .sink(receiveValue: { value in
            receivedValue = value
            expectation.fulfill()
        })
        .store(in: &cancellables)
    
    sut.handleSomeUserAction()
    
    // then
    wait(for: [expectation], timeout: 1.0)
    let unwrappedValue = try XCTUnwrap(receivedValue)
    XCTAssertEqual(unwrappedValue, value1)
}

场景2:需要验证两次发送的顺序和值(value1 → value2)

如果需要同时验证两次发送的正确性,比如确认先发出value1,异步操作完成后发出value2,只需把expectation的expectedFulfillmentCount设为2,同时收集所有结果来验证顺序:

func test_handleSomeUserAction_sendsValue1ThenValue2() {
    // given
    let expectation = expectation(description: "Receive both value1 and value2 in order")
    expectation.expectedFulfillmentCount = 2
    var receivedValues: [ValueType] = []
    var cancellables = Set<AnyCancellable>()
    
    // when
    sut.someOtherStream
        .dropFirst()
        .sink(receiveValue: { value in
            receivedValues.append(value)
            expectation.fulfill()
        })
        .store(in: &cancellables)
    
    sut.handleSomeUserAction()
    
    // then
    wait(for: [expectation], timeout: 1.0)
    XCTAssertEqual(receivedValues.count, 2)
    XCTAssertEqual(receivedValues[0], value1)
    XCTAssertEqual(receivedValues[1], value2)
}

进阶优化:使用XCTAsyncSequence(iOS 16+/macOS 13+)

如果你的项目支持较新的系统版本,苹果提供的XCTAsyncSequence可以让异步测试更简洁,不需要手动管理XCTestExpectation:

func test_handleSomeUserAction_withAsyncSequence() async throws {
    // given
    let stream = sut.someOtherStream
        .dropFirst()
        .prefix(2) // 取前两次发送的事件
        .values // 转换为AsyncSequence
    
    // when
    sut.handleSomeUserAction()
    
    // then
    let receivedValues = try await Array(stream)
    XCTAssertEqual(receivedValues[0], value1)
    XCTAssertEqual(receivedValues[1], value2)
}

内容的提问来源于stack exchange,提问作者PrepareFor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 18:57:48