如何在 Swift 中模拟 TimerPublisher? --“自动连接”不能用于“任何 ConnectablePublisher<Date, Never>”

问题描述 投票:0回答:1

我正在尝试测试我的客户端代码,该代码使用Apple的

Timer.publish(every:on:in:)

我想控制单元测试中的时间,以避免使用

wait(for:timeout:)
并能够同步测试所有内容。

查看文档中的

TimerPublisher
发现它符合ConnectablePublisher
,为了抽象出这一点,我为假定时器和工厂制作了一些样板代码:

import Foundation import Combine protocol TimerFactory { static func makeTimer() -> any ConnectablePublisher<Date, Never> } enum FakeTimerFactory { static func makeTimer() -> any ConnectablePublisher<Date, Never> { return FakeTimer() } } struct FakeTimer: ConnectablePublisher { func receive<S>(subscriber: S) where S : Subscriber, Never == S.Failure, Date == S.Input { } typealias Output = Date typealias Failure = Never func connect() -> Cancellable { return FakeCancellable() } struct FakeCancellable: Cancellable { func cancel() { } } }
但是,使用以下示例代码我无法在 

autoconnect()

 上调用 
ConnectablePublisher
 扩展方法:

FakeTimerFactory.makeTimer() .autoconnect() .sink { _ in print("Tick!") }
我收到以下错误:

Member 'autoconnect' cannot be used on value of type 'any ConnectablePublisher<Date, Never>'; consider using a generic constraint instead
作为参考,我希望此代码可以直接替代 

Timer.publish(every:on:in:)

:

Timer.publish(every: 1.0, on: .main, in: .default) .autoconnect() .sink { _ in print("Tick!") }
我仍然需要弄清楚如何实现虚拟时间,但当前的错误似乎是一个存在类型问题。

问题

    怎么
  1. autoconnect()
    不可用?
  2. 有没有更好的测试方法
  3. TimerPublisher
编辑

代码应该与

RealTimerFactory

 兼容,如下所示:

enum RealTimerFactory: TimerFactory { static func makeTimer() -> any ConnectablePublisher<Date, Never> { // these parameters are hard-coded for brevity // ideally we would pass this in as `makeTimer(every:on:in:)` return Timer.publish(every: 1.0, on: .main, in: .default) } }
这样我们就可以交换测试代码和生产代码之间的实现:

final class UsesTimers { var timerFactory: TimerFactory.Type init(timerFactory: TimerFactory.Type = RealTimerFactory.self) { self.timerFactory = timerFactory } func doSomethingWithTimers() { timerFactory.makeTimer() .autoconnect() // Error occurs here .sink { _ in print("Tick") } } }
    
swift unit-testing timer combine existential-type
1个回答
0
投票

autoconnect

 不可用,因为其签名提到 
Self
:

func autoconnect() -> Publishers.Autoconnect<Self>
对于存在类型,

Self

在编译时是未知的。


您可以通过为可连接的发布者编写自己的类型橡皮擦来轻松摆脱存在类型,类似于一般发布者的

AnyPublisher

struct AnyConnectablePublisher<Output, Failure: Error>: ConnectablePublisher { let upstream: any ConnectablePublisher<Output, Failure> func connect() -> any Cancellable { upstream.connect() } func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input { upstream.receive(subscriber: subscriber) } init(connectable upstream: any ConnectablePublisher<Output, Failure>) { self.upstream = upstream } init<Upstream>(publisher upstream: Upstream) where Self.Failure == Never, Upstream: Publisher, Upstream.Output == Self.Output, Upstream.Failure == Never { self.upstream = upstream.makeConnectable() } }
为了方便起见,我使用 

makeConnectable

 添加了一个额外的初始化程序来包装任何通用发布者。

然后可以声明工厂协议的方法返回

AnyConnectablePublisher<Date, Never>

。在这里,我展示了一个示例,其中假计时器输出 1970 年的前 10 秒。

enum FakeTimerFactory: TimerFactory { static func makeTimer() -> AnyConnectablePublisher<Date, Never> { AnyConnectablePublisher( publisher: (0..<10).publisher .flatMap(maxPublishers: .max(1)) { i in Just(Date(timeIntervalSince1970: Double(i))) .delay(for: 1, scheduler: RunLoop.main) } ) } }
实时工厂直接返回即可

AnyConnectablePublisher(connectable: Timer.publish(every: 1.0, on: .main, in: .default))


在测试的背景下,我想您希望精确控制

什么发布何时。我认为更方便的方法是直接注入 AnyConnectablePublisher

,而不是工厂。这样,您就可以注入一个包含 
AnyConnectablePublisher
PassthroughSubject

let subject = PassthroughSubject<Date, Never>() let timer = AnyConnectablePublisher(publisher: subject) let thingToBeTested = ThingToBeTested(timer: timer) // now you can control the publishing by calling `subject.send(someDate)`
或者您可以将 

makeTimer

 创建为 
instance 方法,并注入工厂的 instance,而不是元类型。然后 PassthroughSubject
 就可以存在于工厂实例中。

© www.soinside.com 2019 - 2024. All rights reserved.