2016-10-25 5 views
1

Im essayant d'obtenir l'injection de dépendance avec les protocoles à Swift :)Mocking une classe avec des extensions Rx

Mon système testé appelle .rx.signIn() sur la dépendance, ce qui signifie qu'elle a étendu réactive où la base: ConcreteObject .

Est-il possible de configurer un protocole prêt à recevoir des appels .rx?

Tout petit exemple ou une alternative serait grandement apprécié.

Répondre

1

J'ai eu le même problème lors de la mise à niveau de RxSwift2 vers RxSwift3. Dans RxSwift2, j'ai simplement utilisé un protocole pour les méthodes rx dans mon code de production (ex: rx_JSON()) et injecté un simple objet mock adhérant à ce protocole dans mes tests unitaires.

Après un court détour dans les médicaments génériques, type associé et d'effacement de type I réglé avec la configuration suivante, qui correspond le mieux à ma configuration RxSwift2-:

Par exemple, pour cacher URLSession que la mise en œuvre concrète de mes appels HTTP, J'ai créé un protocole RemoteClient:

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

Puis dans mon code de production, je référence seulement ce protocole:

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 
      } 
    } 
} 

dans mon tests unitaires, je créé des objets fantaisie qui adhèrent au protocole 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() 
     } 
    } 
} 

Et les réinjecter dans le système sous test:

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) 
    } 
} 

Alors je me cache simplement le rx-espace derrière mon protocole. Je fais cela pour l'instant en utilisant un adaptateur:

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) 
    } 
}