Как ждать ответа от тестируемого компонента, использующего Alamofire? - Xcode - PullRequest
0 голосов
/ 07 апреля 2019

У меня есть контроллер вида входа в систему, который использует библиотеку Almofire для получения ответа.Я выполняю модульное тестирование на этом контроллере, но тест всегда проваливается.Я думаю, потому что нужно время, чтобы ответить.

Мой тестовый пример:

override func setUp() {

    super.setUp()
    continueAfterFailure = false
    let vc = UIStoryboard(name: "Main", bundle: nil)
    controllerUnderTest = vc.instantiateViewController(withIdentifier: "LoginVC") as! LoginViewController
    controllerUnderTest.loadView()

}

override func tearDown() {
    // Put teardown code here. This method is called after the invocation of each test method in the class.
    controllerUnderTest = nil
    super.tearDown()
}

func testLoginWithValidUserInfo() {
    controllerUnderTest.email?.text = "raghad"
    controllerUnderTest.pass?.text = "1234"
    controllerUnderTest.loginButton?.sendActions(for: .touchUpInside)
    XCTAssertEqual(controllerUnderTest.lblValidationMessage?.text , "logged in successfully")
}

Я пытаюсь использовать:

waitForExpectations (время ожидания: 60, обработчик: ноль)

Но я получил эту ошибку:

перехватил «NSInternalInconsistencyException»

Функция almofire в предъявителе входа в систему:

    func sendRequest(withParameters parameters: [String : String]) {
    Alamofire.request(LOGINURL, method: .post, parameters: parameters).validate ().responseJSON { response in
        debugPrint("new line : \(response)" )
        switch response.result {
        case .success(let value):
            let userJSON = JSON(value)
            self.readResponse(data: userJSON)
        case .failure(let error):
            print("Error \(String(describing: error))")
            self.delegate.showMessage("* Connection issue ")

        }
        self.delegate.removeLoadingScreen()
        //firebase log in
        Auth.auth().signIn(withEmail: parameters["email"]!, password: parameters["pass"]!) { [weak self] user, error in
            //guard let strongSelf = self else { return }
            if(user != nil){
                print("login with firebase")

            }
            else{
                print("eroor in somthing")
            }
            if(error != nil){
                print("idon now")
            }
            // ...
        }
    }

}

func readResponse(data: JSON) {
    switch data["error"].stringValue  {
    case "true":
        self.delegate.showMessage("* Invalid user name or password")
    case "false":
        if  data["state"].stringValue=="0" {
            self.delegate.showMessage("logged in successfully")

        }else {
            self.delegate.showMessage("* Inactive account")
        }
    default:

        self.delegate.showMessage("* Connection issue")

    }
}

Как я могу решить эту проблемупроблема?(

Ответы [ 2 ]

1 голос
/ 07 апреля 2019

Привет @ Raghad ak , добро пожаловать в переполнение стека ?.

Ваше предположение о том, что время препятствует успешному прохождению теста, верно.

Сетевой код является асинхронным. После тестовых вызовов .sendActions(for: .touchUpInside) на вашей кнопке входа в систему она переходит на следующую строку, не давая возможности обратного вызова выполнить.

Как и в ответе @ ajeferson , в долгосрочной перспективе я бы рекомендовал размещать ваши вызовы Alamofire за классом обслуживания или просто протоколом, чтобы вы могли заменить их на double в тестах.

Если вы не пишете интеграционные тесты, в которых вы будете тестировать поведение вашей системы в реальном мире, попадание в сеть может принести вам больше вреда, чем пользы. В этом посте более подробно рассказывается, почему это так.

Сказав все это, вот быстрый способ пройти тест. По сути, вам нужно найти способ заставить тест ждать своего завершения асинхронного кода, и вы можете сделать это с уточненным асинхронным ожиданием.

В вашем тесте вы можете сделать это:

expectation(
  for: NSPredicate(
    block: { input, _ -> Bool in
      guard let label = input as? UILabel else { return false }
        return label.text == "logged in successfully"
      }
    ),
    evaluatedWith: controllerUnderTest.lblValidationMessage,
    handler: .none
)

controllerUnderTest.loginButton?.sendActions(for: .touchUpInside)

waitForExpectations(timeout: 10, handler: nil)

Это ожидание будет запускать NSPredicate в цикле и выполняться только тогда, когда предикат вернет true.

0 голосов
/ 07 апреля 2019

Вы должны как-то сигнализировать вашим тестам, что они безопасны для продолжения (т.е. ожидание выполнено).Идеальным подходом было бы отделить этот код от Alamofire и высмеивать его поведение при тестировании.Но просто чтобы ответить на ваш вопрос, вы можете сделать следующее:

В вашем контроллере вида:

func sendRequest(withParameters parameters: [String : String], completionHandler: (() -> Void)?) {

  ...

  Alamofire.request(LOGINURL, method: .post, parameters: parameters).validate ().responseJSON { response in

    ...

    // Put this wherever appropriate inside the responseJSON closure
    completionHandler?()
  }
}

Затем в ваших тестах:

func testLoginWithValidUserInfo() {
    controllerUnderTest.email?.text = "raghad"
    controllerUnderTest.pass?.text = "1234"
    controllerUnderTest.loginButton?.sendActions(for: .touchUpInside)
    let expectation = self.expectation(description: "logged in successfully)
    waitForExpectations(timeout: 60, handler: nil)

    controllerUnderTest.sendRequest(withParameters: [:]) {
      expectation.fulfill()
    }

    XCTAssertEqual(controllerUnderTest.lblValidationMessage?.text , "logged in successfully")
}

Iзнаю, что у вас есть некоторые промежуточные функции между нажатием кнопки и вызовом функции sendRequest, но это только для вас, чтобы понять.Надеюсь, это поможет!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...