【问题标题】:How to wait until get the response from component under test that use Alamofire? - Xcode如何等到得到使用 Alamofire 的被测组件的响应? - Xcode
【发布时间】:2019-04-07 13:16:02
【问题描述】:

我有一个登录视图控制器,用户使用 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(timeout: 60, handler: nil)

但是我收到了这个错误:

捕获“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")

    }
}

我该如何解决这个问题? :(

【问题讨论】:

  • 首先要确保满足您的期望。您的登录视图控制器中可能有一些完成处理程序。在您的测试中,等待该处理程序触发并调用expectation.fulfill()
  • 我没听懂你的意思:(
  • 好吧,在您的登录视图控制器中,您调用了异步运行的 Alamofire。像Alamofire.request ... .response { ... 这样的东西。当响应闭包中的代码执行时,您必须确保调用expectation.fulfill()。如果您可以在登录视图控制器中发布 Alamofire 调用,我可以为您提供更多帮助。
  • 如果你没有达到预期,你就会陷入困境并且永远不会通过。
  • 非常感谢您的帮助。我发布了我的 almofire 功能以了解:(

标签: swift unit-testing cocoapods xcode10.2


【解决方案1】:

您好@Raghad ak,欢迎来到 Stack Overflow ?。

您对阻止测试成功的时间流逝的猜测是正确的。

网络代码是异步的。测试在您的登录按钮上调用.sendActions(for: .touchUpInside) 后,它会移动到下一行,而不会给回调运行机会。

就像@ajeferson 的回答所暗示的那样,从长远来看,我建议将您的 Alamofire 调用放在服务类或协议之后,这样您就可以在测试中将它们替换为 double

除非您编写集成测试来测试系统在现实世界中的行为,否则访问网络可能弊大于利。 This post 详细说明了为什么会这样。

说了这么多,这里有一个快速通过测试的方法。基本上,您需要找到一种方法让测试等待您的异步代码完成,并且您可以通过改进的异步期望来做到这一点。

在您的测试中,您可以这样做:

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 时实现。

【讨论】:

  • 非常感谢。当我使用您的代码时,错误消失了:)
【解决方案2】:

您必须以某种方式向您的测试发出可以安全进行的信号(即满足预期)。理想的方法是解耦 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")
}

我知道您在单击按钮和调用 sendRequest 函数之间有一些中间函数,但这只是为了让您了解一下。希望对您有所帮助!

【讨论】:

  • 我在 sendRequest 函数中遇到了新错误:使用未解析的标识符“completionHandler”;您的意思是“NSAssertionHandler”吗? .当我将其更改为 NSAssertionHandler?() 时出现新错误:无法为类型“NSAssertionHandler?”调用初始化程序?没有参数
  • 您能否向我推荐任何使用 almofire 测试视图控制器的教程?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-04-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-03
  • 2020-02-18
  • 2020-07-23
相关资源
最近更新 更多