Integration Testing Best Practices
This guide covers best practices for writing integration tests in TrustWeave.
Overview
Integration tests verify that multiple components work together correctly. They may use real services (via TestContainers) or test networks.
TestContainers Usage
LocalStack (AWS Services)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Testcontainers
class AwsKmsIntegrationTest : KmsIntegrationTest () {
companion object {
@JvmStatic
val localStack = LocalStackContainer . create ()
}
override fun getKms (): KeyManagementService {
val config = AwsKmsConfig . builder ()
. region ( "us-east-1" )
. endpointOverride ( localStack . getKmsEndpoint ())
. build ()
return AwsKeyManagementService ( config )
}
}
HashiCorp Vault
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Testcontainers
class VaultKmsIntegrationTest : KmsIntegrationTest () {
companion object {
@JvmStatic
val vault = VaultContainer . create ()
}
override fun getKms (): KeyManagementService {
val config = VaultKmsConfig . builder ()
. vaultUrl ( vault . getVaultUrl ())
. token ( vault . getRootToken ())
. build ()
return VaultKeyManagementService ( config )
}
}
Ganache (Ethereum)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Testcontainers
class EthereumIntegrationTest : ChainIntegrationTest () {
companion object {
@JvmStatic
val ganache = GanacheContainer . create ()
}
override fun getChainClient (): BlockchainAnchorClient {
val config = EthereumOptions (
rpcUrl = ganache . rpcEndpoint
)
return EthereumBlockchainAnchorClient ( "eip155:1337" , config )
}
}
Test Isolation
Each integration test should be isolated:
1
2
3
4
5
6
7
8
9
10
11
@BeforeEach
override fun setUp () {
super . setUp ()
// Additional setup
}
@AfterEach
override fun tearDown () {
super . tearDown ()
// Additional cleanup
}
Retry Logic
Use retry for flaky operations:
1
2
3
4
5
6
7
@Test
fun testFlakyOperation () = runBlocking {
retry ( maxAttempts = 3 , delayMs = 1000 ) {
val result = operation ()
assertNotNull ( result )
}
}
Timeout Handling
Set appropriate timeouts:
1
2
3
4
5
6
@Test
fun testWithTimeout () = runBlocking {
assertEventually ( timeoutSeconds = 30 ) {
condition . isMet ()
}
}
Test Scenarios
Use reusable scenarios:
1
2
3
4
5
6
7
8
9
10
11
@Test
fun testCredentialLifecycle () = runBlocking {
val scenario = CredentialLifecycleScenario ( fixture )
scenario . execute ()
}
@Test
fun testMultiplePlugins () = runBlocking {
val scenario = MultiPluginScenario ( fixture )
scenario . testMultipleDidMethods ( listOf ( method1 , method2 ))
}
Best Practices
Use TestContainers : Prefer TestContainers over real services
Tag Tests : Tag integration tests with @Tag("integration")
Isolate Tests : Each test should be independent
Cleanup : Always clean up resources
Retry Flaky Tests : Use retry logic for network operations
Set Timeouts : Configure appropriate timeouts
Skip When Needed : Skip tests if services unavailable
Skipping Integration Tests
1
2
3
4
5
6
# Skip integration tests
./gradlew test -PskipIntegrationTests = true
# Or set environment variable
export VERICORE_SKIP_INTEGRATION_TESTS = true
./gradlew test
Next Steps