ChatScreenViewModel的AsyncStream轮询功能单元测试失败,Mock未正常发射数据求排查
ChatScreenViewModel的AsyncStream轮询功能单元测试失败,Mock未正常发射数据求排查
我帮你排查了一下代码,核心问题出在AsyncStream的初始化逻辑和测试断言的时机上,具体分析和修复方案如下:
1. 根本原因:AsyncStream闭包延迟执行导致Continuation未初始化
你写的MockMessageProvider里,AsyncStream的初始化闭包是只有当流被订阅(也就是开始for await迭代)时才会执行的。测试里你调用sut.onAppear()后立刻调用emit,但此时后台Task可能还没开始遍历流,闭包没执行,continuation还是nil,自然发不出任何数据。
另外,你的MessageProviderUseCase协议里的poll方法标记了async其实是多余的——创建AsyncStream是同步操作,不需要异步执行,这也会导致测试里的异步流程混淆。
2. 修复步骤
第一步:修正Mock和协议定义
先去掉协议和Mock中多余的async,确保continuation能在流创建后被正确初始化:
// 修改协议 protocol MessageProviderUseCase: Sendable { func poll(interval: TimeInterval) -> AsyncStream<MessagePollResult> // 移除async } // 修改Mock private class MockMessageProvider: MessageProviderUseCase { private var continuation: AsyncStream<MessagePollResult>.Continuation? private(set) var pollCallCount: Int = 0 private(set) var pollIntervalInput: TimeInterval? func poll(interval: TimeInterval) -> AsyncStream<MessagePollResult> { pollCallCount += 1 pollIntervalInput = interval return AsyncStream<MessagePollResult> { continuation in self.continuation = continuation } } func emit(_ value: MessagePollResult) { continuation?.yield(value) } func finish() { continuation?.finish() } }
第二步:修正测试的断言时机
原来的defer块会在函数结束时同步执行,但sink的回调是异步的,此时messages数组可能还没接收到更新。改用XCTestExpectation来等待异步事件完成:
@Test func onAppearShouldReturnInitialMessagesAndStartPolling() async throws { let mockMessageProvider = MockMessageProvider() let sut = createSUT(messageProvider: mockMessageProvider) // 创建期望对象,等待异步事件 let initialEmptyExpectation = expectation(description: "Received initial empty messages") let polledMessagesExpectation = expectation(description: "Received polled messages") var messages: [Message] = [] let cancellable = sut.$messages.sink { incoming in messages.append(incoming) switch messages.count { case 1: // 初始空数组 XCTAssertEqual(incoming.count, 0) initialEmptyExpectation.fulfill() case 2: // 收到轮询返回的消息 XCTAssertGreaterThan(incoming.count, 0) polledMessagesExpectation.fulfill() default: break } } defer { cancellable.cancel() } sut.onAppear() // 先等待初始空数组被发送,确保流已经被订阅,continuation已初始化 await fulfillment(of: [initialEmptyExpectation], timeout: 1.0) // 现在再发射测试数据 mockMessageProvider.emit(MessagePollResult.success(fakeMessages)) // 等待轮询消息被接收 await fulfillment(of: [polledMessagesExpectation], timeout: 1.0) // 最后断言poll方法被调用次数 XCTAssertEqual(mockMessageProvider.pollCallCount, 1) }
3. 额外说明
如果你因为真实业务逻辑必须保留poll方法的async,那只需要确保在测试中等待流被订阅后再调用emit——上面的initialEmptyExpectation已经帮你做到了这一点,因为只有当流开始迭代,sut.$messages才会收到初始的空数组。
这样修改后,你的测试应该就能正常捕获到Mock发射的数据了。
内容来源于stack exchange
相关产品推荐
相关产品推荐

