使用 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)
}
}
我正在尝试使用 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)
}
}