You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 11:57:20