基于Quick/Nimble编写MyItemsPresenter单元测试Mock类方法
问题描述
在单元测试开发场景中,我所编写的 MyItemsPresenter 类实现代码如下:
class MyItemsPresenter { internal var deliveryType: ServiceType = .typeA var myItemSections: [MySections] = [.sectionA, .sectionB, .sectionC] var didMoveDeals = false private(set) var childViewControllers = [UIViewController]() var segmentTitles: [String] { myItemSections.map { $0.title } } var selectedOption: String? func createChildViewControllers() -> [UIViewController] { var childViewControllers = [UIViewController]() for item in myItemSections { switch item { case .sectionA: // 注:原代码此处枚举值前多了空格,已修正避免语法错误 childViewControllers.append(MyListWireframe.assembleModuleA()) case .sectionB: childViewControllers.append(MyListWireframe.assembleModuleB()) case .sectionC: childViewControllers.append(MyListWireframe.assembleModuleC()) default: break } } self.childViewControllers = childViewControllers return childViewControllers } private func resetSections() { myItemSections = getOriginalSections() } private func getOriginalSections() -> [MySections] { [.list, .buyItAgain, .clippedDeals, .lastOrder] } func showFirstTwoMenuItems() { if myItemSections.count > 2 { let upperRange = myItemSections.count-1 myItemSections.removeSubrange(ClosedRange(uncheckedBounds: (lower: 2, upper: upperRange))) childViewControllers.removeSubrange(ClosedRange(uncheckedBounds: (lower: 2, upper: upperRange))) } } }
我目前使用Quick和Nimble作为单元测试框架,为完成测试用例编写,需要为上述类实现对应的Mock Presenter类MyItemsPresenterMock,请问该Mock类的具体实现代码应当如何编写?
实现方案
手写适配Quick+Nimble的Mock类核心目标是隔离真实依赖(避免触发MyListWireframe全链路组装逻辑)、记录方法调用行为、支持自定义返回桩,不需要引入额外Mock生成库即可满足绝大多数测试场景,具体实现如下:
@testable import 替换为你的项目主Target名称 import UIKit import Quick import Nimble class MyItemsPresenterMock: MyItemsPresenter { // 方法调用记录标记 var createChildViewControllersCalled: Bool = false var createChildViewControllersCallCount: Int = 0 var showFirstTwoMenuItemsCalled: Bool = false var showFirstTwoMenuItemsCallCount: Int = 0 // 返回值桩,测试时可自定义赋值 var stubbedChildViewControllers: [UIViewController] = [] override func createChildViewControllers() -> [UIViewController] { createChildViewControllersCalled = true createChildViewControllersCallCount += 1 // 优先返回自定义桩值 if !stubbedChildViewControllers.isEmpty { self.childViewControllers = stubbedChildViewControllers return stubbedChildViewControllers } // 无自定义桩时返回空白测试VC,不触发真实业务模块组装 let testControllers = Array(repeating: UIViewController(), count: myItemSections.count) self.childViewControllers = testControllers return testControllers } override func showFirstTwoMenuItems() { showFirstTwoMenuItemsCalled = true showFirstTwoMenuItemsCallCount += 1 // 需要验证方法真实逻辑时保留super调用,仅需要验证调用行为时可以注释掉super调用 super.showFirstTwoMenuItems() } }
使用注意事项
- 必须通过
@testable导入项目主模块,才能访问原类中internal权限的属性,不需要修改原类的访问级别 - 所有带
Called、CallCount后缀的属性,可以直接在Nimble断言中使用,验证方法是否被调用、调用次数是否符合预期,示例断言:expect(mock.createChildViewControllersCallCount).to(equal(1)) - 如果测试场景需要依赖特定的子控制器数组,直接给
stubbedChildViewControllers赋值即可,完全隔离真实业务依赖 - 不建议在Mock中重写原类的私有方法,私有方法逻辑请通过公开方法的行为间接验证
- 原类默认初始化的
myItemSections和getOriginalSections()返回值不一致,编写reset相关测试用例时需要留意这个差异。
内容的提问来源于stack exchange,提问作者user19374490
相关产品推荐
相关产品推荐

