Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/swift/20.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Swift UISearchController';s isActive属性可以';在单元测试中不能设置为true_Swift_Unit Testing_Uikit_Xctest_Uisearchcontroller - Fatal编程技术网

Swift UISearchController';s isActive属性可以';在单元测试中不能设置为true

Swift UISearchController';s isActive属性可以';在单元测试中不能设置为true,swift,unit-testing,uikit,xctest,uisearchcontroller,Swift,Unit Testing,Uikit,Xctest,Uisearchcontroller,我正在为UITableViewController的数据源编写单元测试,该数据源有一个用于过滤结果的UISearchController。我需要测试NumberOfRowsInSection中的逻辑,以便当搜索控制器处于活动状态时,数据源从过滤数组而不是普通数组返回计数 该函数通过检查搜索控制器的“isActive”是否为真/假来控制此操作 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -

我正在为UITableViewController的数据源编写单元测试,该数据源有一个用于过滤结果的UISearchController。我需要测试NumberOfRowsInSection中的逻辑,以便当搜索控制器处于活动状态时,数据源从过滤数组而不是普通数组返回计数

该函数通过检查搜索控制器的“isActive”是否为真/假来控制此操作

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return searchController.isActive ? filteredResults.count : results.count
}
所以我的单元测试是这样写的

func testNumberOfRowsInSection() {
    XCTAssertFalse(searchController.isActive)
    XCTAssertEqual(4, dataSource.tableView(tableView, numberOfRowsInSection: 0))
    
    searchController.isActive = true
    XCTAssertTrue(searchController.isActive) // Fails right after setting it true
    XCTAssertEqual(0, dataSource.tableView(tableView, numberOfRowsInSection: 0)) // Fails
    
    searchController.isActive = false
    XCTAssertFalse(searchController.isActive)
    XCTAssertEqual(4, dataSource.tableView(tableView, numberOfRowsInSection: 0))
}
因此,在单元测试中将“isActive”属性设置为true后,它不会立即保持true。这很奇怪,因为在应用程序的常规运行期间,我可以在视图控制器中将其设置为true,并保持活动状态

override func viewDidLoad() {
    super.viewDidLoad()
    
    // ...
    
    searchController.isActive = true // Makes search bar immediately visible
    
    // ...
}
文档显示它是一个可设置的属性,那么为什么将它设置为true在单元测试中没有任何作用呢?我也试着将它连接到导航栏上,但这并没有改变任何事情。如果我不能在单元测试中像这样测试它,我将不得不模拟该功能,这将很烦人,因为测试它应该很简单

更新示例:

class MovieSearchDataSourceTests: XCTestCase {

private var window: UIWindow!
private var controller: UITableViewController!
private var searchController: UISearchController!
private var sut: MovieSearchDataSource!

override func setUp() {
    window = UIWindow()
    controller = UITableViewController()
    searchController = UISearchController()
    
    sut = MovieSearchDataSource(tableView: controller.tableView,
                                searchController: searchController,
                                movies: Array(repeating: Movie.test, count: 4))
    
    window.rootViewController = UINavigationController(rootViewController: controller)
    controller.navigationItem.searchController = searchController
}

override func tearDown() {
    window = nil
    controller = nil
    searchController = nil
    sut = nil
    RunLoop.current.run(until: Date())
}

func testNumberOfRowsInSection() {
    window.addSubview(controller.tableView)
    controller.loadViewIfNeeded()
    
    XCTAssertFalse(searchController.isActive)
    XCTAssertEqual(4, sut.tableView(controller.tableView, numberOfRowsInSection: 0))
    
    searchController.isActive = true
    XCTAssertTrue(searchController.isActive)
    XCTAssertEqual(0, sut.tableView(controller.tableView, numberOfRowsInSection: 0))
    
    searchController.isActive = false
    XCTAssertFalse(searchController.isActive)
    XCTAssertEqual(4, sut.tableView(controller.tableView, numberOfRowsInSection: 0))
}

}当我面对这样的挑战时,我会使用两种工具:

  • 执行运行循环
  • 将视图控制器的视图放入真实窗口
  • 我编写了一个不起作用的单元测试,然后尝试了这些技巧。窗户戏法奏效了

    func test_canSetWhetherSearchControllerIsActive() throws {
        putInViewHierarchy(sut)
    
        sut.searchController.isActive = false
        XCTAssertFalse(sut.searchController.isActive)
    
        sut.searchController.isActive = true
        XCTAssertTrue(sut.searchController.isActive)
    }
    
    func putInViewHierarchy(_ vc: UIViewController) {
        let window = UIWindow()
        window.addSubview(vc.view)
    }
    
    注意:当您使用窗口技巧时,视图控制器不会在测试结束时被释放,除非我们也启动运行循环。在测试功能结束后,必须在拆卸中完成此操作:

    override func tearDownWithError() throws {
        sut = nil
        executeRunLoop()
        try super.tearDownWithError()
    }
    
    func executeRunLoop() {
        RunLoop.current.run(until: Date())
    }
    

    在UIWindow无法正常工作后,我的解决方案是用协议模拟UISearchController

    // Protocol in main project
    protocol Activatable: AnyObject {
        var isActive: Bool { get set }
    }
    
    extension UISearchController: Activatable { }
    
    final class MovieSearchDataSource: NSObject {
        private var movies: [Movie] = []
        private var filteredMovies: [Movie] = []
    
        private let tableView: UITableView
        private let searchController: Activatable
    
        init(tableView: UITableView, searchController: Activatable, movies: [Movie] = []) {
            self.tableView = tableView
            self.searchController = searchController
            self.movies = movies
            super.init()
        }
    }
    
    extension MovieSearchDataSource: UITableViewDataSource {
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return searchController.isActive ? filteredMovies.count : movies.count
        }
    }
    
    // Unit test file
    class MockSearchController: Activatable {
        var isActive = false
    }
    
    class MovieSearchDataSourceTests: XCTestCase {
    
    private var tableView: UITableView!
    private var searchController: MockSearchController!
    private var sut: MovieSearchDataSource!
    
    override func setUp() {
        tableView = UITableView()
        searchController = MockSearchController()
        
        sut = MovieSearchDataSource(tableView: tableView,
                                    searchController: searchController,
                                    movies: Array(repeating: Movie.test, count: 4))
        
    }
    
    override func tearDown() {
        tableView = nil
        searchController = nil
        sut = nil
    }
    
    func testNumberOfRowsInSection() {
        XCTAssertFalse(searchController.isActive)
        XCTAssertEqual(4, sut.tableView(tableView, numberOfRowsInSection: 0))
        
        searchController.isActive = true
        XCTAssertTrue(searchController.isActive)
        XCTAssertEqual(0, sut.tableView(tableView, numberOfRowsInSection: 0))
        
        searchController.isActive = false
        XCTAssertFalse(searchController.isActive)
        XCTAssertEqual(4, sut.tableView(tableView, numberOfRowsInSection: 0))
    }
    

    }

    我明白你的意思,但这对我不起作用。我将数据源与视图控制器分离,以避免在测试中设置整个控制器堆栈。然而,即使像您所说的那样将数据源放入虚拟控制器并将tableview添加为窗口子视图,iActive也不会坚持。我已经在我的描述中添加了更多的代码,如果您能展示示例代码的其余部分,这将有所帮助。涉及UISearchController甚至表视图的测试不是单元测试。您应该模拟控制器并只测试被测系统。您的代码使用术语
    sut
    ,但无法理解其含义。
    sut
    不应该涉及苹果的代码!我们知道它的作用。这应该是你的代码。在尝试了另一种方法之后,我可以同意模拟控制器是最终更简单的解决方案。我不明白的是为什么你说有一个表视图使它不是一个单元测试。在没有表视图调用的情况下,如何在
    表视图:numberofrowsinssection
    中测试代码逻辑?您已经回答了自己的问题。测试你的逻辑。