The MVP pattern in mobile application development is a fairly easy way to unload the ViewController and put some of the logic into the presenter. Presenter begins to grow into logic, which is easy to test.
Let there is a MelodyListViewController
screen showing a list of tunes. It has a MelodyListPresenter
presenter, which tells ViewController what to show. Data presenter will be taken from the service MelodyService
. MelodyService
is a wrapper over the database and api client that loads melodies. If the network is available, the service takes data from api, otherwise from the database. The types of download errors are presented in the 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 }
Having built such a screen structure, you can do testing. Namely, the testing of data retrieval by the presenter. Presenter is dependent on MelodyService
, so you need to mokirovat this protocol. We agree that Melody
has a static method mocks
, which returns a list of arbitrary melodies.
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) }
Also mokiruem ViewController.
class MelodyListViewControllerMock: MelodyListViewController { var shownMelodies: [Melody]? var shownError: ServiceRequestError? func showMelodies(melodies: [Melody]) { shownMelodies = melodies } func showLoadError(error: ServiceRequestError) { shownError = error } }
ServiceRequestMock
is a protocol that has a single func mock<T>(result: ServiceRequestResult, model: T) -> Promise<T>
method func mock<T>(result: ServiceRequestResult, model: T) -> Promise<T>
, which is returned by Promise. In this Promise, either melodies or a download error are protected — something that is transmitted as a simulated result.
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) } } } }
Thus, we have provided everything necessary for testing the presenter.
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) } } }
As a result, we have a handy tool for writing tests.
Source: https://habr.com/ru/post/425069/
All Articles