Le modèle MVP dans le développement d'applications mobiles est un moyen assez simple de décharger le ViewController et de retirer une partie de la logique du présentateur. Le présentateur commence à acquérir une logique facile à tester.
Soit un écran MelodyListViewController
affichant une liste de mélodies. Il a un présentateur MelodyListPresenter
qui dit à ViewController quoi montrer. Le présentateur prendra les données du service MelodyService
. MelodyService
est un wrapper sur la base de données et le client api qui télécharge les mélodies. Si le réseau est disponible, le service prend les données de l'API, sinon de la base de données. Les types d'erreurs de chargement sont présentés dans l'énumération ServiceRequestError
.
protocol MelodyListViewController: class { func showMelodies(melodies: [Melody]) func showLoadError(error: ServiceRequestError) } protocol MelodyListPresenter { var view: MelodyListViewController? { get } var melodyService: MelodyService { get } func fetchMelodies() -> Promise<Void> } extension MelodyListPresenter { func fetchMelodies() -> Promise<Void> { return melodyService.getMelodies().done { melodies in self.view?.showMelodies(melodies: melodies) }.catch { error in self.view?.showLoadError(error: error) } } } protocol MelodyService { func getMelodies() -> Promise<[Melody]> } public enum ServiceRequestError: Error { case unknownError case noNetwork case noData }
Après avoir construit une telle structure d'écran, vous pouvez faire le test. À savoir, tester la réception des données par le présentateur. Le présentateur a une dépendance MelodyService
, vous devez donc vous moquer de ce protocole. Soyons d'accord que Melody
a une méthode de mocks
statique qui renvoie une liste de morceaux arbitraires.
class MelodyServiceMock: MelodyService, ServiceRequestMock { var emulatedResult: ServiceRequestResult = .error(.unknownError) func getMelodies() -> Promise<[Melody]> { let melodies = Melody.mocks() return mock(result: emulatedResult, model: melodies) } } enum ServiceRequestResult { case success case error(ServiceRequestError) }
Nous avons également mouillé ViewController.
class MelodyListViewControllerMock: MelodyListViewController { var shownMelodies: [Melody]? var shownError: ServiceRequestError? func showMelodies(melodies: [Melody]) { shownMelodies = melodies } func showLoadError(error: ServiceRequestError) { shownError = error } }
ServiceRequestMock
est un protocole qui a une seule func mock<T>(result: ServiceRequestResult, model: T) -> Promise<T>
, qui renvoie Promise. Dans cette promesse, les morceaux ou une erreur de démarrage sont protégés - ce qui est transmis comme résultat simulé.
protocol ServiceRequestMock { func mock<T>(result: ServiceRequestResult, model: T) -> Promise<T> } extension ServiceRequestMock { func mock<T>(result: ServiceRequestResult, model: T) -> Promise<T> { return Promise { seal in switch result { case .success: return seal.fulfill(model) case .error(let requestError): return seal.reject(requestError) } } } }
Ainsi, nous avons fourni tout le nécessaire pour tester le présentateur.
import XCTest import PromiseKit class MelodyListPresenterTests: XCTestCase { let view = MelodyListViewControllerMock() let melodyService = MelodyServiceMock() var presenter: MelodyListPresenterImp! override func setUp() { super.setUp() presenter = MelodyListPresenterImp( melodyService: melodyService, view: view) view.presenter = presenter } func test_getMelodies_success() { // given let melodiesMock = Melody.mocks() melodyService.emulatedResult = .success // when let fetchMelodies = presenter.fetchMelodies() // then fetchMelodies.done { melodies in XCTAssertNotNil(self.view.shownMelodies) XCTAssert(self.view.shownMelodies == melodiesMock) }.catch { _ in XCTFail("Failed melodies upload") } } func test_getMelodies_fail() { // given melodyService.emulatedResult = .error(.noNetwork) // when let fetchMelodies = presenter.fetchMelodies() // then fetchMelodies.done { melodies in XCTFail("Mistakenly uploaded melodies") }.catch { _ in XCTAssertNotNil(self.view.shownError) XCTAssert(self.view.shownError is ServiceRequestError) XCTAssert(self.view.shownError as! ServiceRequestError == .noNetwork) } } }
En conséquence, nous avons obtenu un outil pratique pour écrire des tests.