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

单元测试中如何比较INStartCallContactResolutionResult对象

问题描述

我正在对INStartCallIntent对应的Intent Handler类进行单元测试,但在对比联系人解析返回的结果对象时遇到了问题。

例如,我有如下基础的INStartCallIntent处理类:

import Intents

class StartCallHandler: NSObject, INStartCallIntentHandling {
        func resolveContacts(for intent: INStartCallIntent, with completion: @escaping ([INStartCallContactResolutionResult]) -> Void) {
        guard let contacts = intent.contacts, !contacts.isEmpty, let person = contacts.first else {
            completion([.needsValue()])
            return
        }
        
        guard contacts.count == 1 else {
            completion([.unsupported(forReason: .multipleContactsUnsupported)])
            return
        }

        let matchingContacts = [person] // matching logic here
        switch matchingContacts.count {
        case 2  ... Int.max:
            // We need Siri's help to ask user to pick one from the matches.
            completion([.disambiguation(with: matchingContacts)])
        case 1:
            // We have exactly one matching contact
            completion([.success(with: person)])
        default:
            completion([.unsupported(forReason: .noContactFound)])
        }
    }
}

我编写了如下简单单元测试,但无法对INStartCallContactResolutionResult对象做相等判断:

func testResolveContacts() {
    let person = INPerson(personHandle: INPersonHandle(value: nil, type: .unknown), nameComponents: nil, displayName: "Steve Jobs", image: nil, contactIdentifier: nil, customIdentifier: nil)
    let intent = INStartCallIntent(audioRoute: .unknown, destinationType: .unknown, contacts: [person], recordTypeForRedialing: .unknown, callCapability: .audioCall)
    let handler = StartCallHandler()
        
    handler.resolveContacts(for: intent) { result in
        XCTAssertEqual(result.count, 1)
        guard let firstResult = result.first else { return XCTFail() }
        
        let expectedPerson = INPerson(personHandle: INPersonHandle(value: nil, type: .unknown), nameComponents: nil, displayName: "Steve Jobs", image: nil, contactIdentifier: nil, customIdentifier: nil)
        let expectedResult = INStartCallContactResolutionResult(.success(with: expectedPerson))
        XCTAssertEqual(firstResult, expectedResult)
    }
}

XCTAssertEqual断言失败,错误信息如下:

XCTAssertEqual failed: ("<INStartCallContactResolutionResult:
0x600002109310> {
resolutionResultCode = Success;
resolvedValue = <INPerson: 0x600002c7b780> {
displayName = Steve Jobs;
contactIdentifier = ;
nameComponents = ;
image = ;
customIdentifier = ;
relationship = ;
siriMatches = ;
personHandle = <INPersonHandle: 0x600000d78960> {
value = ;
type = Unknown;
label = ;
};
};
disambiguationItems = ;
itemToConfirm = ;
unsupportedReason = 0; }") is not equal to ("<INStartCallContactResolutionResult: 0x6000021092c0> {
resolutionResultCode = Success;
resolvedValue = <INPerson: 0x600002c7b900> {
displayName = Steve Jobs;
contactIdentifier = ;
nameComponents = ;
image = ;
customIdentifier = ;
relationship = ;
siriMatches = ;
personHandle = <INPersonHandle: 0x600000d78d80> {
value = ;
type = Unknown;
label = ;
};
};
disambiguationItems = ;
itemToConfirm = ;
unsupportedReason = 0; }")

可见两个对象的所有属性完全一致,但断言仍然失败,推测是苹果官方未为该类型实现相等判定方法。这导致我无法完成该函数的测试,请问有没有其他方案可以实现该测试场景?

解决方案

出现这个问题的原因是苹果的Intent相关类型没有实现原生的相等判断,默认XCTAssertEqual对NSObject子类是判断指针是否相同,两个不同实例就算属性完全一致也会判定为不相等,可通过以下两种方案实现测试:

方案1:拆解属性分别断言(最稳妥)

分别校验分辨率结果的状态码、关联的联系人属性即可,不需要直接对比整个结果对象,修改后的测试代码示例如下:

func testResolveContacts() {
    let expectedDisplayName = "Steve Jobs"
    let person = INPerson(personHandle: INPersonHandle(value: nil, type: .unknown), nameComponents: nil, displayName: expectedDisplayName, image: nil, contactIdentifier: nil, customIdentifier: nil)
    let intent = INStartCallIntent(audioRoute: .unknown, destinationType: .unknown, contacts: [person], recordTypeForRedialing: .unknown, callCapability: .audioCall)
    let handler = StartCallHandler()
        
    handler.resolveContacts(for: intent) { result in
        XCTAssertEqual(result.count, 1)
        guard let firstResult = result.first else { return XCTFail() }
        
        // 校验分辨率结果状态为成功
        XCTAssertEqual(firstResult.resolutionResultCode, .success)
        // 校验解析到的联系人属性符合预期
        guard let resolvedPerson = firstResult.resolvedValue else {
            return XCTFail("成功状态下应该返回解析后的联系人")
        }
        XCTAssertEqual(resolvedPerson.displayName, expectedDisplayName)
        XCTAssertEqual(resolvedPerson.personHandle?.type, .unknown)
        XCTAssertNil(resolvedPerson.personHandle?.value)
        // 其他你需要校验的属性都可以在这里加断言
    }
}

如果需要测试其他分支(比如多联系人、无匹配联系人的情况),同样可以校验对应的resolutionResultCode、unsupportedReason、disambiguationItems等属性即可。

方案2:封装相等判断辅助方法

如果多个测试用例都需要对比INStartCallContactResolutionResult,可以写一个全局的辅助判断函数,复用校验逻辑:

func resolutionResultEqual(_ lhs: INStartCallContactResolutionResult, _ rhs: INStartCallContactResolutionResult) -> Bool {
    guard lhs.resolutionResultCode == rhs.resolutionResultCode else { return false }
    switch lhs.resolutionResultCode {
    case .success:
        guard let lhsPerson = lhs.resolvedValue, let rhsPerson = rhs.resolvedValue else { return false }
        return lhsPerson.displayName == rhsPerson.displayName
        && lhsPerson.personHandle == rhsPerson.personHandle
        && lhsPerson.contactIdentifier == rhsPerson.contactIdentifier
        // 其他需要对比的属性
    case .unsupported:
        return lhs.unsupportedReason == rhs.unsupportedReason
    case .disambiguation:
        guard let lhsItems = lhs.disambiguationItems, let rhsItems = rhs.disambiguationItems else { return false }
        // 对比待选列表的属性
        return lhsItems.count == rhsItems.count
    default:
        return true
    }
}

// 测试时使用
XCTAssertTrue(resolutionResultEqual(firstResult, expectedResult))

内容的提问来源于stack exchange,提问作者johnny

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 03:27:03