Section 11/161 menit
11. Testing Passkeys di Simulator dan Device
11. Testing Passkeys di Simulator dan Device
Testing di Simulator (Xcode 15+)
swift
// Simulator mendukung passkey sejak Xcode 15
// Aktifkan: Simulator → Features → Face ID (untuk simulasi biometrik)
// Dalam test, gunakan real passkey flow — simulator mendukung penuh
func testPasskeyRegistration() async throws {
// Test di simulator: biometrik akan prompt Face ID enrollment dialog
// Terima di Features → Face ID → Enrolled
// Gunakan actual ASAuthorizationController — tidak perlu mock khusus
let viewModel = RegistrationViewModel()
await viewModel.registerPasskey(username: "test@example.com")
XCTAssertNotNil(viewModel.registeredCredentialID)
}
Protocol-Based Testing Architecture
swift
// Protocol untuk dependency injection
protocol PasskeyManagerProtocol {
func register(username: String, challenge: Data) async throws -> RegistrationResult
func signIn(challenge: Data) async throws -> AssertionResult
}
// Implementasi nyata
class PasskeyManager: PasskeyManagerProtocol {
func register(username: String, challenge: Data) async throws -> RegistrationResult {
let coordinator = AuthenticationCoordinator()
let credential = try await coordinator.registerPasskey(
username: username,
userID: Data(username.utf8),
challenge: challenge,
rpID: "example.com"
)
return RegistrationResult(from: credential)
}
func signIn(challenge: Data) async throws -> AssertionResult {
let coordinator = AuthenticationCoordinator()
let assertion = try await coordinator.signInWithPasskey(
challenge: challenge,
rpID: "example.com"
)
return AssertionResult(from: assertion)
}
}
// Mock untuk unit test
class MockPasskeyManager: PasskeyManagerProtocol {
var shouldSucceed = true
var registrationResult: RegistrationResult?
var assertionResult: AssertionResult?
func register(username: String, challenge: Data) async throws -> RegistrationResult {
if !shouldSucceed { throw ASAuthorizationError(.failed) }
return registrationResult ?? .mockSuccess
}
func signIn(challenge: Data) async throws -> AssertionResult {
if !shouldSucceed { throw ASAuthorizationError(.canceled) }
return assertionResult ?? .mockSuccess
}
}
// ViewModel yang testable
class LoginViewModel {
private let passkeyManager: PasskeyManagerProtocol
init(passkeyManager: PasskeyManagerProtocol = PasskeyManager()) {
self.passkeyManager = passkeyManager
}
var isSignedIn = false
var errorMessage: String?
func signIn() async {
do {
let challenge = try await server.fetchChallenge()
let result = try await passkeyManager.signIn(challenge: challenge)
try await server.verifyAssertion(result)
isSignedIn = true
} catch let error as ASAuthorizationError where error.code == .canceled {
// User cancel — tidak tampilkan error
} catch {
errorMessage = error.localizedDescription
}
}
}
// Unit test tanpa simulator atau device
func testSignInSuccess() async {
let mock = MockPasskeyManager()
mock.shouldSucceed = true
let viewModel = LoginViewModel(passkeyManager: mock)
await viewModel.signIn()
XCTAssertTrue(viewModel.isSignedIn)
XCTAssertNil(viewModel.errorMessage)
}
func testSignInCanceled() async {
let mock = MockPasskeyManager()
mock.shouldSucceed = false
let viewModel = LoginViewModel(passkeyManager: mock)
await viewModel.signIn()
XCTAssertFalse(viewModel.isSignedIn)
XCTAssertNil(viewModel.errorMessage) // Cancel tidak tampilkan error
}