如何等到使用 Alamofire 的被测组件得到响应? - Xcode
How to wait until get the response from component under test that use Alamofire? - Xcode
我有一个登录视图控制器,用户 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)
但是我得到了这个错误:
caught "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")
}
}
我该如何解决这个问题? :(
您必须以某种方式向您的测试发出可以安全继续进行的信号(即满足预期)。理想的方法是在测试时解耦 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
函数之间有一些中间函数,但这只是为了让您了解这个想法。希望对您有所帮助!
嗨 @Raghad ak,欢迎来到 Stack Overflow。
您对阻止测试成功的时间流逝的猜测是正确的。
网络代码是异步的。测试在您的登录按钮上调用 .sendActions(for: .touchUpInside)
后,它会移动到下一行,而不会给 运行.
回调机会
在测试中喜欢 @ajeferson's answer suggests, in the long run I'd recommend placing your Alamofire calls behind a service class or just a protocol, so that you can replace them with a 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
,并且仅在谓词returns true
.
时实现
我有一个登录视图控制器,用户 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)
但是我得到了这个错误:
caught "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")
}
}
我该如何解决这个问题? :(
您必须以某种方式向您的测试发出可以安全继续进行的信号(即满足预期)。理想的方法是在测试时解耦 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
函数之间有一些中间函数,但这只是为了让您了解这个想法。希望对您有所帮助!
嗨 @Raghad ak,欢迎来到 Stack Overflow。
您对阻止测试成功的时间流逝的猜测是正确的。
网络代码是异步的。测试在您的登录按钮上调用 .sendActions(for: .touchUpInside)
后,它会移动到下一行,而不会给 运行.
在测试中喜欢 @ajeferson's answer suggests, in the long run I'd recommend placing your Alamofire calls behind a service class or just a protocol, so that you can replace them with a 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
,并且仅在谓词returns true
.