O padrão MVP no desenvolvimento de aplicativos móveis é uma maneira bastante simples de descarregar o ViewController e remover parte da lógica do apresentador. O apresentador começa a adquirir uma lógica fácil de testar.
Haja uma tela MelodyListViewController
mostrando uma lista de melodias. Ele tem um apresentador MelodyListPresenter
que diz ao ViewController o que mostrar. O apresentador MelodyService
dados do serviço MelodyService
. MelodyService
é um invólucro do banco de dados e da API que baixa melodias. Se a rede estiver disponível, o serviço coletará dados da API, caso contrário, do banco de dados. Os tipos de erros de carregamento são apresentados na enumeração 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 }
Tendo construído essa estrutura de tela, você pode fazer o teste. Ou seja, testando o recebimento de dados pelo apresentador. O apresentador tem uma dependência do MelodyService
, portanto, você precisa zombar deste protocolo. Vamos concordar que o Melody
tem um método de mocks
estática que retorna uma lista de músicas arbitrárias.
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) }
Também molhamos o ViewController.
class MelodyListViewControllerMock: MelodyListViewController { var shownMelodies: [Melody]? var shownError: ServiceRequestError? func showMelodies(melodies: [Melody]) { shownMelodies = melodies } func showLoadError(error: ServiceRequestError) { shownError = error } }
ServiceRequestMock
é um protocolo que possui um único método func mock<T>(result: ServiceRequestResult, model: T) -> Promise<T>
, que retorna Promise. Nesta promessa, músicas ou um erro de inicialização são protegidos - o que é transmitido como resultado simulado.
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) } } } }
Assim, fornecemos tudo o necessário para testar o apresentador.
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) } } }
Como resultado, obtivemos uma ferramenta conveniente para escrever testes.