iOS — модульное тестирование асинхронной частной функции в Presenter of MVP

Здравствуйте, я пытаюсь провести модульное тестирование частной функции, расположенной в Presenter.

Это мои коды докладчика, и я использую объект Networking Singleton APIService

class MyPresenter {
  weak var vc: MyProtocol?

  func attachView(vc: MyProtocol?) {
     self.vc = vc
  }

  func request(_ id: String) {
    if id.count == 0 {
      vc?.showIDEmptyAlert()
      return
    }
    fetch(id)
  }

  private func fetch(_ id:String) {
    DispatchQueue.global.async {
      APIService.shared.fetch(id) { (data, err) in 
        if let err = err {
          self.vc?.showErrorAlert()
          return
        }
        self.vc?.update(data)
      }
    }
  }
}

и это мои коды ViewController

class MyViewController: UIViewController, MyProtocol {
  private var presenter: MyPresenter = MyPresenter()

  override func viewDidLoad() {
    super.viewDidLoad()
    presenter.attachView(vc: self)
  }

  func showIDEmptyAlert() {
    self.present .. 
  }

  func showErrorAlert() {
    self.present .. 
  }

  func update(data: String) {
    self.label.text = data 
  }

  @IBAction func handleRegisterButton(_ sender: UIButton) {
    guard let id = idTextField.text else { return }
    presenter.request(id)
  }
}

Это мой Presenter и View. И я написал тестовый код вот так

Во-первых, я сделал Mock PassiveView вот так.

class MyViewMock: MyProtocol {
  private (set) var showEmptyIdAlertHasBeenCalled = false
  private (set) var showErrorAlertHasBeenCalled = false
  private (set) var updateHasBeenCalled = false

  func showEmptyIdAlert() {
    showEmptyIdAlertHasBeenCalled = true
  }
  func showErrorAlert() {
    showErrorAlertHasBeenCalled = true
  }
  func update(data: String) {
    updateHasBeenCalled = true
  }

}

Поэтому я ожидал, что если бы я мог протестировать методы Presenter request(_:) с действительным идентификатором и недействительным

но поскольку request(_:) не получил параметр обработчика, а APIService.shared.fetch является асинхронным, я не смог получить правильный результат, вызвав request(_:). (Всегда ложно)

Как я могу протестировать этот тип Presenter?


person PrepareFor    schedule 11.10.2018    source источник


Ответы (2)


Что касается XCTests, то существует класс XCTestExpectation для тестирования асинхронных функций. Но в вашем подходе к тестированию Presenter есть проблема. Вы должны использовать mock для своей сетевой службы и заглушить ее ожидаемыми аргументами. Звонить в сервис нет смысла. Юнит-тесты — самый быстрый вид тестов. Частные методы являются частью черного ящика, внутреннюю структуру которого вам не нужно волновать. Тестируйте общедоступные интерфейсы, но не тестируйте частные методы. Если вы попытаетесь смоделировать и заглушить APIService, вы заметите, что это невозможно сделать, если это синглтон. В итоге вы получите внедрение службы в качестве зависимости в Presenter для лучшей тестируемости.

Если сервис будет mocked и будет использоваться заглушка, то нет необходимости использовать XCTestExpectation, потому что не будет никакого асинхронного кода.

person Vladimir Kaltyrin    schedule 19.11.2018

Чтобы протестировать асинхронные методы, вы должны использовать XCTestExpectation, чтобы ваш тест ждал завершения асинхронной операции.

это тестовый пример асинхронного метода

// Asynchronous test: success fast, failure slow
func testValidCallToiTunesGetsHTTPStatusCode200() {
    // given
    let url = URL(string: "https://itunes.apple.com/search?media=music&entity=song&term=abba")
    // 1
    let promise = expectation(description: "Status code: 200")

    // when
    let dataTask = sessionUnderTest.dataTask(with: url!) { data, response, error in
        // then
        if let error = error {
            XCTFail("Error: \(error.localizedDescription)")
            return
        } else if let statusCode = (response as? HTTPURLResponse)?.statusCode {
            if statusCode == 200 {
                // 2
                promise.fulfill()
            } else {
                XCTFail("Status code: \(statusCode)")
            }
        }
    }
    dataTask.resume()
    // 3
    waitForExpectations(timeout: 5, handler: nil)
}

Подробнее о модульном тестировании можно прочитать на странице здесь

person Magdy Zamel    schedule 11.10.2018