bcsc 技能测试
- 作者仓库星标 0
- 作者仓库 skills-registry
bcsc-core Native Unit Testing
Overview
This skill guides creation of native unit tests for the packages/bcsc-core module, which contains Swift (iOS) and Kotlin (Android) implementations of authentication, PIN management, keychain storage, token services, and device authentication.
The codebase already uses protocol-based dependency injection on iOS and interface/class-based injection on Android, making it straightforward to create mock implementations for isolated unit testing.
Test Infrastructure
iOS — XCTest via SPM
Tests are built with Swift Package Manager using the package manifest at packages/bcsc-core/Package.swift. The key concepts:
BcscCoreTestabletarget: A subset of production iOS source files compiled for the simulator (without React Native / CocoaPods dependencies). Only files listed in thesources:array are included.BcscCoreTeststest target: XCTest files underios/BcscCoreTests/, compiled with@testable import BcscCoreTestable.SPM_BUILDflag: A Swift compiler define set inPackage.swiftviaswiftSettings: [.define("SPM_BUILD")]. The fileios/SPMCompat.swiftuses#if SPM_BUILDto provide lightweight stubs (e.g.,SecureRandom,Data.arrayOfBytes()) for types from JWE files that aren't included in the SPM target.- Scheme name: xcodebuild uses
BcscCoreTests-Package(SPM auto-appends-Package).
Adding a new iOS source file to the test target
If the code under test references types in a file not yet in Package.swift, add it to the sources: array in the BcscCoreTestable target. If that file depends on types from excluded files (e.g., JWE/crypto), add stubs to SPMCompat.swift behind #if SPM_BUILD.
Adding a new iOS test file
- Create a new
.swiftfile inios/BcscCoreTests/. - Import frameworks and the testable module:
import LocalAuthentication // if testing LAPolicy/biometric types import XCTest @testable import BcscCoreTestable - Write
XCTestCasesubclasses. Follow the naming convention{ClassUnderTest}Tests. - Run with
yarn test:iosor directly:xcodebuild test -scheme BcscCoreTests-Package \ -destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' \ -skipPackagePluginValidation
Android — JUnit 4 + MockK
Tests live under android/src/test/java/com/bcsccore/ mirroring the main source package structure.
Dependencies (in android/build.gradle)
testImplementation 'junit:junit:4.13.2'
testImplementation 'io.mockk:mockk:1.13.8'
testImplementation 'org.robolectric:robolectric:4.11.1'
testImplementation 'androidx.test:core:1.5.0'
Adding a new Android test file
- Create a Kotlin file in
android/src/test/java/com/bcsccore/{subpackage}/, matching the source package. - Write standard JUnit 4 test classes with
@Testmethods. - Use
io.mockk.mockk<T>()andevery { ... } returns ...for mocking. - Run with
yarn test:androidor directly:cd android && ./gradlew test
Running all tests
yarn test # runs test:ios then test:android
yarn test:ios # iOS only
yarn test:android # Android only
Mocking Strategy
iOS — Protocol Conformance
The codebase defines protocols for all service boundaries. Create mock classes that conform to the protocol and expose controllable properties:
final class MockPINService: PINServiceProtocol {
var verifyResult: String? // control return value
var setPINResult: String = "mock-hash"
var deletePINCalled = false // track side effects
func verifyPINAndGetHash(issuer _: String, accountID _: String, pin _: String) -> String? {
return verifyResult
}
func setPIN(issuer _: String, accountID _: String, pin _: String, isAutoGenerated _: Bool) throws -> String {
return setPINResult
}
func deletePIN(issuer _: String, accountID _: String) throws {
deletePINCalled = true
}
// ... remaining protocol methods
}
Pattern: Use var resultProperty for controlling return values and var methodCalled = false booleans for verifying side effects.
Available iOS protocols for mocking
| Protocol | File | Purpose |
|---|---|---|
PINServiceProtocol |
PINServiceProtocol.swift |
PIN set/verify/delete/hash |
PINKeychainServiceProtocol |
PINKeychainService.swift |
Keychain secret storage |
PINCryptoPolicyProtocol |
PINCryptoPolicy.swift |
Salt/key/iteration config |
CommonCryptoWrapperProtocol |
CommonCryptoWrapper.swift |
PBKDF2 key derivation |
LAContextProtocol |
LAContext+Extensions.swift |
Biometric evaluation |
DeviceAuthServiceProtocol |
DeviceAuthService.swift |
Device authentication |
TokenStorageServiceProtocol |
TokenService.swift |
Token keychain storage |
KeyPairManagerProtocol |
KeyPairManager.swift |
Cryptographic key pairs |
Android — MockK
Use MockK's mockk<T>() for mocking classes and interfaces:
val mockPinService = mockk<PinService>()
every { mockPinService.validatePIN(any(), "1234") } returns true
every { mockPinService.hasPIN(any()) } returns true
val account = Account(id = "test", issuer = "https://idsit.gov.bc.ca", clientID = "client")
val result = account.verifyPIN("1234", mockPinService)
assertTrue(result.success)
For data classes (e.g., NativeAccount), test serialization round-trips with Gson directly — no mocking needed.
Test Patterns
Time-dependent tests (eg. PIN penalty logic)
The penalty system locks users out after failed PIN attempts. Both platforms accept an injectable "current time" parameter to make tests deterministic.
iOS: Pass verifyDate: parameter:
let start = Date()
for i in 0..<5 {
_ = account.verifyPIN("wrong", pinService: mock, verifyDate: start.addingTimeInterval(Double(i)))
}
// Advance past penalty before next batch
let afterPenalty = start.addingTimeInterval(65)
Android: Pass timestamp to verifyPIN and isServingPenalty:
val now = System.currentTimeMillis()
account.verifyPIN("wrong", mockPinService, now)
val penalty = account.isServingPenalty(now)
Important: When testing escalating penalties, you must advance time past each penalty tier before triggering the next. Calling verifyPIN while a penalty is active will return "locked" without incrementing the attempt counter.
Penalty tiers: 5 attempts → 1 min, 10 → 10 min, 15 → 1 hour, 20 → 1 day.
NSCoding / Serialization round-trips
iOS — Use NSKeyedArchiver / NSKeyedUnarchiver:
let data = try NSKeyedArchiver.archivedData(withRootObject: original, requiringSecureCoding: true)
let decoded = try NSKeyedUnarchiver.unarchivedObject(ofClass: Account.self, from: data)
XCTAssertEqual(decoded?.id, original.id)
Android — Use Gson:
val json = gson.toJson(original)
val restored = gson.fromJson(json, NativeAccount::class.java)
assertEquals(original.uuid, restored.uuid)
Test naming conventions
iOS: test{Behavior} in camelCase — e.g., testVerifyPINReturnsSuccessOnCorrectPIN
Android: Backtick-quoted descriptive names — e.g., `verifyPIN returns success when PIN is correct`
Helper factories
Android — Create a createAccount() helper with defaults to reduce boilerplate:
private fun createAccount(
id: String = "test-uuid",
issuer: String = "https://idsit.gov.bc.ca",
clientID: String = "test-client-id",
securityMethod: AccountSecurityMethod = AccountSecurityMethod.PIN_NO_DEVICE_AUTH,
): Account = Account(id = id, issuer = issuer, clientID = clientID, securityMethod = securityMethod)
Checklist for adding tests
- Identify the class/struct to test and which platform(s)
- Check if the source file is already in
Package.swiftsources:(iOS) — add if missing - Check if new dependencies need stubs in
SPMCompat.swift(iOS) - Create mock(s) for any protocol/interface dependencies
- Place test file in
ios/BcscCoreTests/orandroid/src/test/java/com/bcsccore/{subpackage}/ - For time-dependent logic, use injectable timestamps rather than
Date()/System.currentTimeMillis() - Run
yarn testand verify all tests pass on both platforms
<!-- tomevault:4.0:skill_md:2026-05-23 -->Source: bcgov/bc-wallet-mobile — distributed by TomeVault.
- 流狐分类
- 写作
- 作者声明 Agent
- 未找到明确声明;不据此推断已兼容或已测试
- 静态检查
- 88 / 100 · 启发式扫描,不代表运行安全
- 作者 / 版本 / 许可
- @tomevault-io · 未声明 license
- 流狐 Token 估算
- 低消耗
- 流狐接入估算
- 即装即用
- 是否需要外部 API Key
- 未发现要求
- 检测到的系统要求
- 未声明
- 底层运行要求
- 未声明
- 检测到的文件与系统行为
-
- 只读
- 允许写入 / 修改
- 检测到的网络行为
- 仅限本地
- 安装命令数
- 无(仅作为资料)
档案由构建时根据 SKILL.md 与安装命令自动衍生,可能与作者实际意图存在差异。
需要注意: 未限定 allowed-tools,默认拥有全部工具权限。
作者没有在当前 SKILL.md 中定义固定输出样例。 This skill guides creation of native unit tests for the packages/bcsc-core module, which contains Swift (iOS) and Kotlin (Android) implementations of authentication, PIN management, keychain storage, token services, and device authentication.
Test Infrastructure
Tests are built with Swift Package Manager using the package manifest at packages/bcsc-core/Package.swift. The key concepts: BcscCoreTestable target: A subset of production iOS source files compiled for the simulator (without React Native / CocoaPods…
Tests live under android/src/test/java/com/bcsccore/ mirroring the main source package structure. Dependencies (in android/build.gradle) Adding a new Android test file
Running all tests
Mocking Strategy
# bcsc-core Native Unit Testing
## Overview
This skill guides creation of native unit tests for the `packages/bcsc-core` module, which contains Swift (iOS) and Kotlin (Android) implementations of authentication, PIN management, keychain storage, token services, and device authentication.
The codebase already uses **protocol-based dependency injection** on iOS and **interface/class-based injection** on Android, making it straightforward to create mock implementations for isolated unit testing.
## Test Infrastructure
### iOS — XCTest via SPM
Tests are built with Swift Package Manager using the package manifest at `packages/bcsc-core/Package.swift`. The key concepts:
- **`BcscCoreTestable` target**: A subset of production iOS source files compiled for the simulator (without React Native / CocoaPods dependencies). Only files listed in the `sources:` array are included.
- **`BcscCoreTests` test target**: XCTest files under `ios/BcscCoreTests/`, compiled with `@testable import BcscCoreTestable`.
- **`SPM_BUILD` flag**: A Swift compiler define set in `Package.swift` via `swiftSettings: [.define("SPM_BUILD")]`. The file `ios/SPMCompat.swift` uses `#if SPM_BUILD` to provide lightweight stubs (e.g., `SecureRandom`, `Data.arrayOfBytes()`) for types from JWE files that aren't included in the SPM target.
- **Scheme name**: xcodebuild uses `BcscCoreTests-Package` (SPM auto-appends `-Package`).
#### Adding a new iOS source file to the test target
If the code under test references types in a file not yet in `Package.swift`, add it to the `sources:` array in the `BcscCoreTestable` target. If that file depends on types from excluded files (e.g., JWE/crypto), add stubs to `SPMCompat.swift` behind `#if SPM_BUILD`.
#### Adding a new iOS test file
… 作者原文负责流程事实;流狐只索引当前章节、要点、文件与命令。
章节 -> Overview → Test Infrastructure → iOS — XCTest via SPM → Android — JUnit 4 + MockK → Running all tests → Mocking Strategy
要点 -> protocol-based dependency injection · interface/class-based injection · BcscCoreTestable target · BcscCoreTests test target · SPMBUILD flag · Scheme name · Pattern · iOS
文件/命令 -> packages/bcsc-core · packages/bcsc-core/Package.swift · BcscCoreTestable · sources: · BcscCoreTests · ios/BcscCoreTests/ · @testable import BcscCoreTestable · SPMBUILD
内容 SHA-256 -> 9ce69e88c59e
原文结构
适用与边界
原文中的明确线索
packages/bcsc-core、packages/bcsc-core/Package.swift、BcscCoreTestable、sources:、BcscCoreTests、ios/BcscCoreTests/、@testable import BcscCoreTestable、SPMBUILD