Testing presenter using PromiseKit

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 screen MelodyListViewControllershowing a list of tunes. He has a presenter MelodyListPresenterwho tells ViewController what to show. Data presenter will be taken from the service MelodyService. MelodyServiceThis is a wrapper over the database and api client, loading melodies. If the network is available, the service takes data from api, otherwise from the database. Types of loading errors are presented in enum ServiceRequestError.


protocolMelodyListViewController: class{
    funcshowMelodies(melodies: [Melody])funcshowLoadError(error: ServiceRequestError)
}
protocolMelodyListPresenter{
    var view: MelodyListViewController? { get }
    var melodyService: MelodyService { get }
    funcfetchMelodies() -> Promise<Void>
}
extensionMelodyListPresenter{
    funcfetchMelodies() -> Promise<Void> {
        return melodyService.getMelodies().done { melodies inself.view?.showMelodies(melodies: melodies)
        }.catch { error inself.view?.showLoadError(error: error)
        }
    }
}
protocolMelodyService{
    funcgetMelodies() -> Promise<[Melody]>
}
publicenumServiceRequestError: Error{
    case unknownError
    case noNetwork
    case noData
}

Having built such a screen structure, you can do testing. Namely, the testing of data acquisition by the presenter. Presenter is dependent MelodyService, so you need to mokirovat this protocol. We agree that it Melodyhas a static method mocksthat returns a list of arbitrary melodies.


classMelodyServiceMock: MelodyService, ServiceRequestMock{
    var emulatedResult: ServiceRequestResult = .error(.unknownError)
    funcgetMelodies() -> Promise<[Melody]> {
        let melodies = Melody.mocks()
        return mock(result: emulatedResult, model: melodies)
    }
}
enumServiceRequestResult{
    case success
    case error(ServiceRequestError)
}

Also mokiruem ViewController.


classMelodyListViewControllerMock: MelodyListViewController{
    var shownMelodies: [Melody]?
    var shownError: ServiceRequestError?funcshowMelodies(melodies: [Melody]) {
        shownMelodies = melodies
    }
    funcshowLoadError(error: ServiceRequestError) {
        shownError = error
    }
}

ServiceRequestMockThis is the protocol that has the only method func mock<T>(result: ServiceRequestResult, model: T) -> Promise<T>that Promise returns. In this Promise, either melodies or a download error are protected — something that is transmitted as a simulated result.


protocolServiceRequestMock{
    funcmock<T>(result: ServiceRequestResult, model: T) -> Promise<T>
}
extensionServiceRequestMock{
    funcmock<T>(result: ServiceRequestResult, model: T) -> Promise<T> {
        returnPromise { seal inswitch 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
classMelodyListPresenterTests: XCTestCase{
    let view = MelodyListViewControllerMock()
    let melodyService = MelodyServiceMock()
    var presenter: MelodyListPresenterImp!overridefuncsetUp() {
        super.setUp()
        presenter = MelodyListPresenterImp(
            melodyService: melodyService, 
            view: view)
        view.presenter = presenter
    }
    functest_getMelodies_success() {
        // givenlet melodiesMock = Melody.mocks()
        melodyService.emulatedResult = .success
        // whenlet fetchMelodies = presenter.fetchMelodies()
        // then
        fetchMelodies.done { melodies inXCTAssertNotNil(self.view.shownMelodies)
            XCTAssert(self.view.shownMelodies == melodiesMock)
        }.catch { _inXCTFail("Failed melodies upload")
        }
    }
    functest_getMelodies_fail() {
        // given
        melodyService.emulatedResult = .error(.noNetwork)
        // whenlet fetchMelodies = presenter.fetchMelodies()
        // then
        fetchMelodies.done { melodies inXCTFail("Mistakenly uploaded melodies")
        }.catch { _inXCTAssertNotNil(self.view.shownError)
            XCTAssert(self.view.shownError isServiceRequestError)
            XCTAssert(self.view.shownError as! ServiceRequestError == .noNetwork)
        }
    }
}

As a result, we have a handy tool for writing tests.


Also popular now: