Das MVP-Muster bei der Entwicklung mobiler Anwendungen ist eine relativ einfache Möglichkeit, den ViewController auszulagern und einen Teil der Logik in den Präsentator zu platzieren. Der Präsentator beginnt, eine Logik zu erwerben, die leicht zu testen ist.
Es soll einen MelodyListViewController
Bildschirm mit einer Liste von Melodien geben. Er hat einen MelodyListPresenter
Moderator, der ViewController mitteilt, was angezeigt werden soll. Der Präsentator übernimmt Daten vom MelodyService
Dienst. MelodyService
ist ein Wrapper über die Datenbank und den API-Client, der Melodien herunterlädt. Wenn das Netzwerk verfügbar ist, nimmt der Dienst Daten von der API, andernfalls von der Datenbank. Arten von ServiceRequestError
werden in enum 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 }
Nachdem Sie eine solche Bildschirmstruktur erstellt haben, können Sie die Tests durchführen. Testen des Empfangs von Daten durch den Präsentator. Der Präsentator hat eine MelodyService
Abhängigkeit, daher müssen Sie dieses Protokoll verspotten. Lassen Sie uns zustimmen, dass Melody
eine statische mocks
Methode hat, die eine Liste beliebiger Melodien zurückgibt.
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) }
Wir haben auch ViewController nass gemacht.
class MelodyListViewControllerMock: MelodyListViewController { var shownMelodies: [Melody]? var shownError: ServiceRequestError? func showMelodies(melodies: [Melody]) { shownMelodies = melodies } func showLoadError(error: ServiceRequestError) { shownError = error } }
ServiceRequestMock
ist ein Protokoll mit einer einzigen Methode func mock<T>(result: ServiceRequestResult, model: T) -> Promise<T>
, das Promise zurückgibt. In diesem Versprechen werden entweder Melodien oder ein Startfehler geschützt - was als simuliertes Ergebnis übertragen wird.
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) } } } }
Daher haben wir alles Notwendige zum Testen des Präsentators bereitgestellt.
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) } } }
Als Ergebnis haben wir ein praktisches Tool zum Schreiben von Tests erhalten.