рдореЛрдмрд╛рдЗрд▓ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреЗ рд╡рд┐рдХрд╛рд╕ рдореЗрдВ MVP рдкреИрдЯрд░реНрди ViewController рдХреЛ рдЙрддрд╛рд░рдиреЗ рдФрд░ рдкреНрд░рд╕реНрддреЛрддрд╛ рдореЗрдВ рдХреБрдЫ рддрд░реНрдХ рдирд┐рдХрд╛рд▓рдиреЗ рдХрд╛ рдПрдХ рд╕рд░рд▓ рддрд░реАрдХрд╛ рд╣реИред рдкреНрд░рд╕реНрддреБрддрдХрд░реНрддрд╛ рддрд░реНрдХ рдХреЛ рдкреНрд░рд╛рдкреНрдд рдХрд░рдирд╛ рд╢реБрд░реВ рдХрд░ рджреЗрддрд╛ рд╣реИ рдЬреЛ рдкрд░реАрдХреНрд╖рдг рдХрд░рдирд╛ рдЖрд╕рд╛рди рд╣реИред
рдЪрд▓реЛ рдПрдХ MelodyListViewController
рд╕реНрдХреНрд░реАрди рд╣реИ рдЬрд┐рд╕рдореЗрдВ рдзреБрдиреЛрдВ рдХреА рд╕реВрдЪреА рджрд┐рдЦрд╛рдИ рдЬрд╛ рд░рд╣реА рд╣реИред рдЙрдирдХреЗ рдкрд╛рд╕ рдПрдХ MelodyListPresenter
рдкреНрд░рд╕реНрддреБрддрдХрд░реНрддрд╛ рд╣реИ рдЬреЛ ViewController рдмрддрд╛рддрд╛ рд╣реИ рдХрд┐ рдХреНрдпрд╛ рджрд┐рдЦрд╛рдирд╛ рд╣реИред рдкреНрд░рд╕реНрддреБрддрдХрд░реНрддрд╛ MelodyService
рд╕реЗрд╡рд╛ рд╕реЗ рдбреЗрдЯрд╛ MelodyService
ред MelodyService
рдбреЗрдЯрд╛рдмреЗрд╕ рдФрд░ рдПрдкреАрдЖрдИ рдХреНрд▓рд╛рдЗрдВрдЯ рдкрд░ рдПрдХ рд░реИрдкрд░ рд╣реИ рдЬреЛ рдзреБрдиреЛрдВ рдХреЛ рдбрд╛рдЙрдирд▓реЛрдб рдХрд░рддрд╛ рд╣реИред рдпрджрд┐ рдиреЗрдЯрд╡рд░реНрдХ рдЙрдкрд▓рдмреНрдз рд╣реИ, рддреЛ рд╕реЗрд╡рд╛ рдПрдкреАрдЖрдИ рд╕реЗ рдбреЗрдЯрд╛ рд▓реЗрддреА рд╣реИ, рдЕрдиреНрдпрдерд╛ рдбреЗрдЯрд╛рдмреЗрд╕ рд╕реЗред рд▓реЛрдбрд┐рдВрдЧ рддреНрд░реБрдЯрд┐рдпреЛрдВ рдХреЗ рдкреНрд░рдХрд╛рд░ 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 }
рдРрд╕реА рд╕реНрдХреНрд░реАрди рд╕рдВрд░рдЪрдирд╛ рдХрд╛ рдирд┐рд░реНрдорд╛рдг рдХрд░рдиреЗ рдХреЗ рдмрд╛рдж, рдЖрдк рдкрд░реАрдХреНрд╖рдг рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред рдЕрд░реНрдерд╛рддреН, рдкреНрд░рд╕реНрддреБрддрдХрд░реНрддрд╛ рджреНрд╡рд╛рд░рд╛ рдбреЗрдЯрд╛ рдХреА рдкреНрд░рд╛рдкреНрддрд┐ рдХрд╛ рдкрд░реАрдХреНрд╖рдгред рдкреНрд░рд╕реНрддреБрддрдХрд░реНрддрд╛ рдХреЗ рдкрд╛рд╕ MelodyService
рдирд┐рд░реНрднрд░рддрд╛ рд╣реИ, рдЗрд╕рд▓рд┐рдП рдЖрдкрдХреЛ рдЗрд╕ рдкреНрд░реЛрдЯреЛрдХреЙрд▓ рдХрд╛ рдордЬрд╛рдХ MelodyService
рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред рдЖрдЗрдП рд╕рд╣рдордд рд╣реИрдВ рдХрд┐ Melody
рдореЗрдВ рдПрдХ рд╕реНрдереИрддрд┐рдХ mocks
рд╡рд┐рдзрд┐ рд╣реИ рдЬреЛ рдордирдорд╛рдиреА рдзреБрдиреЛрдВ рдХреА рд╕реВрдЪреА рд▓реМрдЯрд╛рддреА рд╣реИред
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) }
рд╣рдордиреЗ ViewController рдХреЛ рднреА рдЧреАрд▓рд╛ рдХрд░ рджрд┐рдпрд╛ред
class MelodyListViewControllerMock: MelodyListViewController { var shownMelodies: [Melody]? var shownError: ServiceRequestError? func showMelodies(melodies: [Melody]) { shownMelodies = melodies } func showLoadError(error: ServiceRequestError) { shownError = error } }
ServiceRequestMock
рдПрдХ рдкреНрд░реЛрдЯреЛрдХреЙрд▓ рд╣реИ рдЬрд┐рд╕рдореЗрдВ рдПрдХ рдПрдХрд▓ рд╡рд┐рдзрд┐ func mock<T>(result: ServiceRequestResult, model: T) -> Promise<T>
, рдЬреЛ рд╡рд╛рджрд╛ рдХрд░рддрд╛ рд╣реИред рдЗрд╕ рд╡рд╛рджреЗ рдореЗрдВ, рдпрд╛ рддреЛ рдзреБрди рдпрд╛ рдПрдХ рдмреВрдЯ рддреНрд░реБрдЯрд┐ рд╕рдВрд░рдХреНрд╖рд┐рдд рд╣реИ - рдПрдХ рдирдХрд▓реА рдкрд░рд┐рдгрд╛рдо рдХреЗ рд░реВрдк рдореЗрдВ рдХреНрдпрд╛ рдкреНрд░рд╕рд╛рд░рд┐рдд рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред
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) } } } }
рдЗрд╕ рдкреНрд░рдХрд╛рд░, рд╣рдордиреЗ рдкреНрд░рд╕реНрддреБрддрдХрд░реНрддрд╛ рдХреЗ рдкрд░реАрдХреНрд╖рдг рдХреЗ рд▓рд┐рдП рдЖрд╡рд╢реНрдпрдХ рд╕рдм рдХреБрдЫ рдкреНрд░рджрд╛рди рдХрд┐рдпрд╛ред
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) } } }
рдирддреАрдЬрддрди, рд╣рдореЗрдВ рдкрд░реАрдХреНрд╖рдг рд▓рд┐рдЦрдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рд╕реБрд╡рд┐рдзрд╛рдЬрдирдХ рдЙрдкрдХрд░рдг рдорд┐рд▓рд╛ред