如何对状态包含StatePointer的Contract进行单元测试?MockServices调用vaultService报错解决方案
Solution for Testing Corda Contracts with LinearPointer References
I’ve run into this exact issue when testing contracts with LinearPointer references in Corda, so I can walk you through two solid solutions depending on your contract’s verification logic:
Option 1: Test Pointer Rules Directly (Avoid Resolving)
If your contract’s verification logic only needs to validate the pointer’s metadata (like target state type or pointerId), you don’t need to actually resolve the pointed-to state. This avoids the MockServices vault limitation entirely.
Example Contract Verification Logic
override fun verify(tx: LedgerTransaction) { val outputState = tx.outputsOfType<YourStateWithPointer>().single() // Validate the pointer targets the correct state type require(outputState.pointer.stateType == TargetState::class.java) { "Pointer must reference a TargetState" } // Validate the pointer points to a valid linear ID (if needed) require(outputState.pointer.pointerId is UniqueIdentifier) { "Pointer must use a UniqueIdentifier" } }
Corresponding Test
@Test fun `validates pointer targets correct state type`() { val notary = MockServices.getMockNotary() val invalidPointerState = YourStateWithPointer( pointer = LinearPointer(WrongState::class.java, UniqueIdentifier()) ) val txBuilder = TransactionBuilder(notary) .addOutputState(invalidPointerState) .addCommand(YourContract.Commands.Create(), listOf(alice.owningKey)) // Expect failure due to wrong pointer type assertThrows<IllegalArgumentException> { txBuilder.toLedgerTransaction(MockServices(listOf("your.contract.package"))).verify() } }
Option 2: Customize MockServices to Support State Resolution
If your contract must resolve the pointed-to state to validate its contents (e.g., checking a field in the target state), you’ll need to extend MockServices with a functional VaultService that can return your pre-defined test states.
Step 1: Create a Mock Vault Service
class TestVaultService(private val testStates: Map<StateRef, ContractState>) : VaultService { // Implement only the methods needed for resolution; others can throw exceptions override fun queryBy( criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, includeSoftDeleted: Boolean ): Page<Vault.State> { // Map our test states to Vault.State instances (simplified for testing) val vaultStates = testStates.map { (ref, state) -> Vault.State( state = state, ref = ref, status = Vault.StateStatus.UNCONSUMED, contractState = state, recordedTime = Instant.now(), consumedTime = null, lockId = null, lockUpdateTime = null ) } return Page(vaultStates, paging, vaultStates.size.toLong()) } // Default implementations for unused methods override fun trackBy( criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, includeSoftDeleted: Boolean ): DataFeed<Page<Vault.State>, Vault.Update> { throw UnsupportedOperationException() } override fun softDelete(states: List<StateRef>, reason: String) = throw UnsupportedOperationException() // Add other required VaultService methods with default UnsupportedOperationException implementations }
Step 2: Extend MockServices with Your Vault
class MockServicesWithVault(testStates: Map<StateRef, ContractState>) : MockServices(listOf("your.contract.package")) { private val customVault = TestVaultService(testStates) override val vaultService: VaultService get() = customVault }
Step 3: Use the Custom MockServices in Tests
@Test fun `validates pointed-to state meets requirements`() { val notary = MockServices.getMockNotary() // Create the target state we want our pointer to reference val targetState = TargetState(requiredField = "validValue", linearId = UniqueIdentifier()) val targetStateRef = StateRef(TxHash.random(), 0) // Map the state ref to our target state for the mock vault val testStatesMap = mapOf(targetStateRef to targetState) // Initialize our custom MockServices with the test state val mockServices = MockServicesWithVault(testStatesMap) // Create our state with a valid pointer to the target state val validState = YourStateWithPointer( pointer = LinearPointer(TargetState::class.java, targetState.linearId) ) val txBuilder = TransactionBuilder(mockServices.networkParameters.notary) .addOutputState(validState) .addCommand(YourContract.Commands.Create(), listOf(alice.owningKey)) // The pointer will now resolve successfully using our mock vault txBuilder.toLedgerTransaction(mockServices).verify() }
Key Notes
- When extending
MockServices, make sure to include your contract’s package in the constructor arguments so Corda can find your contract classes. - Keep the
TestVaultServiceimplementation minimal—only implement the methods your contract’s resolution logic actually uses.
内容的提问来源于stack exchange,提问作者McXD
相关产品推荐
相关产品推荐

