使用 Rx 扩展模拟 class

Mocking a class with Rx extensions

我正在尝试使用 Swift 中的协议实现依赖注入 :)

我的测试系统在依赖项上调用 .rx.signIn(),这意味着它扩展了 Reactive where Base : ConcreteObject.

是否可以设置一个协议来接收 .rx 调用?

任何小例子或替代方案将不胜感激。

我在从 RxSwift2 升级到 RxSwift3 的过程中遇到了同样的问题。在 RxSwift2 中,我只是在我的生产代码中使用了一个用于 rx 方法的协议(例如 rx_JSON()),并在我的单元测试中注入了一个遵循该协议的简单模拟对象。

在短暂绕过泛型、关联类型和类型擦除之后,我确定了以下设置,它与我的 RxSwift2 设置非常匹配:

例如,为了隐藏 URLSession 作为我的 HTTP 调用的具体实现,我创建了一个 RemoteClient 协议:

protocol RemoteClient {
    func json(url: URL) -> Observable<Any>
}

然后在我的生产代码中我只引用该协议:

class SomeService {
    var remoteClient: RemoteClient

    init(remoteClient: RemoteClient) {
        self.remoteClient = remoteClient
    }

    func getSomeData(_ id: String) -> Observable<JSONDictionary> {
        let urlString = "..."

        return remoteClient
            .json(url: URL(string: urlString)!)
            .map { data in
                guard let show = data as? JSONDictionary else {
                    throw ParsingError.json
                }

                return show
            }
    }
}

在我的单元测试中,我创建了遵守 RemoteClient 协议的模拟对象:

class RemoteClientMock: RemoteClient {

    var output: Any?
    var verifyInput: ((URL) -> Void)?

    func json(url: URL) -> Observable<Any> {
        verifyInput?(url)

        return Observable.create { observer in
            if let data = self.output {
                observer.on(.next(data))
            }

            observer.on(.completed)

            return Disposables.create()
        }
    }
}

并将它们注入被测系统:

class SomeServiceTest: TestCase {

    let disposeBag = DisposeBag()


    func testGetSomeData() {
        // setup
        let expectation = self.expectation(description: "request succeeded")

        let remoteClientMock = RemoteClientMock()
        remoteClientMock.verifyInput = { url in
            XCTAssertEqual(URL(string: "some url"), url)
        }
        remoteClientMock.output = ["id": 4711]

        let service = SomeService(remoteClient: remoteClientMock)

        // exercise
        let observable = service.getSomeData("4711")

        // verify
        observable
            .subscribe(onNext: { data in
                XCTAssertEqual(4711, data["id"] as? Int)
                expectation.fulfill()
            })
            .addDisposableTo(disposeBag)

        waitForExpectations(timeout: 1, handler: nil)
    }
}

所以我只是将 rx-namespace 隐藏在我的协议后面。我现在通过使用适配器来做到这一点:

let remoteClient = URLSessionRemoteClientAdapter(urlSession: URLSession.shared)
let service = SomeService(remoteClient: remoteClient)

struct URLSessionRemoteClientAdapter: RemoteClient {
    let urlSession: URLSession

    init(urlSession: URLSession) {
        self.urlSession = urlSession
    }

    func json(url: URL) -> Observable<Any> {
        return urlSession.rx.json(url: url)
    }
}