0

很快,我需要测试删除表格视图单元格。每个单元格都有两个与之关联的行操作,其中一个是删除该单元格。

如何手动调用此行操作的处理程序?这是执行删除的功能

private func deleteCellAtRow(row: Int) {
    let deleteCell = NSIndexPath(forRow: row, inSection: 0)

    // get the actions corresponding to the cell at that index
    let actions : [UITableViewRowAction] = self.tableView(self.queue, editActionsForRowAtIndexPath: deleteCell)!

    // Get a reference to the delete button for that cell
    let predicate : NSPredicate = NSPredicate(format: "title MATCHES[c] '.*delete'")
    if let action = (actions as NSArray).filteredArrayUsingPredicate(predicate).first as? UITableViewRowAction {
        // What to do now??
        // Call the callback 
    }
}
4

2 回答 2

3

不幸的是,我也有同样的需求,但我没有找到一种干净的方法。

无论如何,由于手动触发的目的只是对动作进行单元测试(在我的情况下也是如此),我们可以检查UITableViewRowAction类的私有标头并手动触发所需的 API。显然,这不能在生产中完成,而只是在测试套件中完成,否则应用程序将被拒绝!

注意:我仅在 iOS 9.3 上对此进行了测试,并且由于它使用私有 API,它可能会停止与更新版本的 iOS 一起使用。

解决方案

在测试包的桥接头中添加这些行(或在单独的文件中,只要它是在桥接头中导入的)。

@interface UITableViewRowAction (Private)
@property(nonatomic, strong) void (^_handler)(UITableViewRowAction *, NSIndexPath *);
@end

通过这种方式,我们公开暴露_handlerUITableViewRowAction.

现在,在我们的 swift 文件中,我们可以这样做:

private func deleteCellAtRow(row: Int) {
    let deleteCell = NSIndexPath(forRow: row, inSection: 0)

    // get the actions corresponding to the cell at that index
    let actions : [UITableViewRowAction] = self.tableView(self.queue, editActionsForRowAtIndexPath: deleteCell)!

    // Get a reference to the delete button for that cell
    let predicate : NSPredicate = NSPredicate(format: "title MATCHES[c] '.*delete'")
    if let action = (actions as NSArray).filteredArrayUsingPredicate(predicate).first as? UITableViewRowAction {
        // OUR SOLUTION
        action._handler(action, indexPath)
        // And now you can test!
    }
}
于 2016-08-01T14:26:18.620 回答
1

一个更复杂但可能更面向未来的解决方案是使用接缝。有关接缝的更多信息,请阅读https://qualitycoding.org/mocking-standalone-functions/。我们可以使用同样的技术来模拟初始化器。首先,将以下内容添加到您的项目中:

import UIKit

let defaultUITableViewRowActionInit: (UITableViewRowAction.Style, String?, @escaping (UITableViewRowAction, IndexPath) -> Void) -> UIKit.UITableViewRowAction = { style, title, handler in
  return UIKit.UITableViewRowAction(style: style, title: title, handler: handler)
}
var uiTableViewRowActionInit = defaultUITableViewRowActionInit

func UITableViewRowAction(style: UITableViewRowAction.Style, title: String?, handler: @escaping (UITableViewRowAction, IndexPath) -> Void) -> UIKit.UITableViewRowAction {
  return uiTableViewRowActionInit(style, title, handler)
}

从这里开始,对 UITableVeiwRowAction 的调用将通过我们的函数而不是普通的初始化程序。在正常的 prod 使用期间,行为将是相同的。对于测试,我们现在可以先添加一个 mock:

class UITableViewRowActionMock: UITableViewRowAction {
  var passedStyle: UITableViewRowAction.Style
  var passedTitle: String?
  var passedHandler: (UITableViewRowAction, IndexPath) -> Void

  init(style: UITableViewRowAction.Style, title: String?, handler: @escaping (UITableViewRowAction, IndexPath) -> Void) {
    passedStyle = style
    passedTitle = title
    passedHandler = handler
  }
}

这个类只捕获传递给它的参数,包括块。从这里开始,我们需要我们的测试设置和拆卸来设置和拆卸接缝:

override func setUp() {
  super.setUp()

  // CODE ...

  uiTableViewRowActionInit = { style, title, handler in
    return UITableViewRowActionMock(style: style, title: title, handler: handler)
  }
}

override func tearDown() {
  // CODE ...

  uiTableViewRowActionInit = defaultUITableViewRowActionInit

  super.tearDown()
}

从那里,我们可以运行我们的测试:

func testEditActionSomehow() {
  let indexPath = IndexPath(row: 0, section: 0)
  // here I just grab, first, feel free to do something more complicated
  guard let action = sut.tableView(sut.tableView, editActionsForRowAt: indexPath)?.first as? UITableViewRowActionMock else {
    XCTFail("Unexpected type")
    return
  }

  action.passedHandler(action, indexPath)

  // 

在此处添加断言代码 }

但是,如果您有很多案例要测试,您可能希望将大部分代码从您的操作中提取到更可测试的地方。

于 2018-12-24T22:42:14.050 回答