如何比较 INStartCallContactResolutionResult 对象以进行单元测试?
How can I compare INStartCallContactResolutionResult objects for unit testing?
我正在尝试针对 INStartCallIntent 对我的 Intent 处理程序 class 进行单元测试,但我在比较联系人解析的结果对象时遇到了问题。
例如,给定 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; }")
因此,即使这两个对象具有相同的属性,XCTAssertEqual 也可能会失败,因为 Apple 端没有实现相等函数。
这使得测试此功能变得几乎不可能。有没有人能够通过其他方式完成此操作?
我最后在这里做的是将联系人解析器逻辑放在一个单独的帮助器中 class 并将 INStartCallContactResolutionResult
class 包装到一个自定义枚举中,该枚举基本上只是映射它 1:1.
public enum PersonResolverUnsupportedReason {
case startCallContactUnsupportedReason(INStartCallContactUnsupportedReason)
}
public enum PersonResolverResult {
case success(INPerson)
case disambiguation([INPerson])
case needsValue
case unsupported
case unsupportedWithReason(PersonResolverUnsupportedReason)
case skip
var startCallContactResolutionResult: INStartCallContactResolutionResult {
switch self {
case let .success(person):
return .success(with: person)
case let .disambiguation(persons):
return .disambiguation(with: persons)
case .needsValue:
return .needsValue()
case .unsupported:
return .unsupported()
case let .unsupportedWithReason(reason):
switch reason {
case let .startCallContactUnsupportedReason(startCallReason):
return .unsupported(forReason: startCallReason)
}
case .skip:
return .notRequired()
}
}
}
public protocol PersonResolverProtocol: AnyObject {
func attemptToResolvePerson(_ person: INPerson, with: @escaping ([PersonResolverResult]) -> Void)
}
public class PersonResolver: PersonResolverProtocol {
public func attemptToResolvePerson(_ person: INPerson, with completion: @escaping ([PersonResolverResult]) -> Void) {
let matchingContacts = [person] // matching logic here
switch matchingContacts.count {
case 2...Int.max:
completion([.disambiguation(matchingContacts.map { INPerson(...) })])
case 1:
guard let matchingContact = matchingContacts.first else {
completion([.unsupportedWithReason(.startCallContactUnsupportedReason(.noContactFound))])
break
}
completion([.success(INPerson(...))])
default:
// no contacts match
completion([.unsupportedWithReason(.startCallContactUnsupportedReason(.noContactFound))])
}
}
}
现在我可以:
- 更好地对联系解析器逻辑进行单元测试
- 将此解析器注入 StartCallHandler
- 如果需要,在其他意图处理程序 class 中重新使用联系解析器 class。
我正在尝试针对 INStartCallIntent 对我的 Intent 处理程序 class 进行单元测试,但我在比较联系人解析的结果对象时遇到了问题。
例如,给定 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; }")
因此,即使这两个对象具有相同的属性,XCTAssertEqual 也可能会失败,因为 Apple 端没有实现相等函数。
这使得测试此功能变得几乎不可能。有没有人能够通过其他方式完成此操作?
我最后在这里做的是将联系人解析器逻辑放在一个单独的帮助器中 class 并将 INStartCallContactResolutionResult
class 包装到一个自定义枚举中,该枚举基本上只是映射它 1:1.
public enum PersonResolverUnsupportedReason {
case startCallContactUnsupportedReason(INStartCallContactUnsupportedReason)
}
public enum PersonResolverResult {
case success(INPerson)
case disambiguation([INPerson])
case needsValue
case unsupported
case unsupportedWithReason(PersonResolverUnsupportedReason)
case skip
var startCallContactResolutionResult: INStartCallContactResolutionResult {
switch self {
case let .success(person):
return .success(with: person)
case let .disambiguation(persons):
return .disambiguation(with: persons)
case .needsValue:
return .needsValue()
case .unsupported:
return .unsupported()
case let .unsupportedWithReason(reason):
switch reason {
case let .startCallContactUnsupportedReason(startCallReason):
return .unsupported(forReason: startCallReason)
}
case .skip:
return .notRequired()
}
}
}
public protocol PersonResolverProtocol: AnyObject {
func attemptToResolvePerson(_ person: INPerson, with: @escaping ([PersonResolverResult]) -> Void)
}
public class PersonResolver: PersonResolverProtocol {
public func attemptToResolvePerson(_ person: INPerson, with completion: @escaping ([PersonResolverResult]) -> Void) {
let matchingContacts = [person] // matching logic here
switch matchingContacts.count {
case 2...Int.max:
completion([.disambiguation(matchingContacts.map { INPerson(...) })])
case 1:
guard let matchingContact = matchingContacts.first else {
completion([.unsupportedWithReason(.startCallContactUnsupportedReason(.noContactFound))])
break
}
completion([.success(INPerson(...))])
default:
// no contacts match
completion([.unsupportedWithReason(.startCallContactUnsupportedReason(.noContactFound))])
}
}
}
现在我可以:
- 更好地对联系解析器逻辑进行单元测试
- 将此解析器注入 StartCallHandler
- 如果需要,在其他意图处理程序 class 中重新使用联系解析器 class。