Skip to content

Cordform Containers

If your project uses the Cordform Gradle task (usually named deployNodes), you may find convenient use of it’s output as the source for a Testcontainers-based Corda network during testing.

The corda-testacles-testcontainers module provides two ways to do just that, again based on the nodes directory created by Cordform:

  • CordformNetworkExtension: a convenient JUnit5 extension
  • CordformNetworkContainer: a container type that can be used directly in conjunction with Testcontainers’ @Testcontainers and @Container annotations.

Cordform-based containers may not seem that useful, but they can help around a cluttered classpath for both cordapp and RPC sides while testing. Future versions will support dynamic network configurations without a Cordform’s node folder.

Gradle Config

Optional: To simplify your Gradle builds, make testing tasks dependent to deployNodes:

project.afterEvaluate {
    project.tasks.withType(Test).each {it.dependsOn(":deployNodes")}
}

Alternatively you should be able to use a manual one-liner like:

./gradlew clean assemble deployNodes check

Also, you probably want to increase the Java Heap of node containers by updating each node one in deployNodes with custom.jvmArgs, including a garbage collector as shown bellow:

node {
    name "..."
    //...
    extraConfig = [
            //...
            'custom.jvmArgs': ["-Xmx2G", "-XX:+UseG1GC"]
    ]
}

Cordform Network Extension

An example of using CordformNetworkExtension in a simple RPC test:

/** Sample test using [CordformNetworkExtension] */
@ExtendWith(CordformNetworkExtension::class)
class CordformNetworkExtensionTest {

    companion object {

        // A string-based image name, e.g. one of the 
        // NodeImageNameConfig onstants.
        // Note: Ignored if a [CordaNetworkConfig]-annotated
        // field is present.
        @NodesImageName
        @JvmStatic
        val nodesImageName = NodeImageNameConfig.CORDA_CE_ALPINE_ZULU_4_6_1

        // Optional, defaults to new network
        // Note: Ignored if a [CordaNetworkConfig]-annotated
        // field is present.
        @NodesNetwork
        @JvmStatic
        val nodesNetwork = Network.newNetwork()

        // Optional, defaults to auto-lookup (build/nodes, ../build/nodes)
        // Note: Ignored if a [CordaNetworkConfig]-annotated
        // field is present.
        @NodesDir
        @JvmStatic
        val nodesDir = File(System.getProperty("user.dir"))
                .parentFile.resolve("build/nodes")

        // Optional, provides the Corda network config to the extension.
        // When using this all other extension config annotations
        // will be ignored (@NodesImageName, @NodesNetwork and @NodesDir)
        @CordaNetwork
        @JvmStatic
        val networkConfig: CordaNetworkConfig = CordformNetworkConfig(
                nodesDir = nodesDir,
                imageName = nodesImageName,
                network = nodesNetwork,
                // Set node log level to DEBUG
                imageCordaArgs = "--logging-level DEBUG",
                // Create a Postgres DB for each node (default is H2)
                // The driver will be automatically resolved from 
                // either _{nodeDir}/drivers_ or the classpath
                databaseSettings = CordformDatabaseSettingsFactory.POSTGRES
                        .withTransactionIsolationLevel(READ_COMMITTED))
    }

    // The extension implements a ParameterResolver 
    // for CordformNetworkContainer 
    @Test
    fun `Can send a yo`(nodesContainer: CordformNetworkContainer) {
        val rpcOps = nodesContainer.getNode("partya")
            .getRpc(/* optional user or username */)
        // Do something...
    }
}

Cordform Network Container

When CordformNodesExtension does not provide the right trade-off between abstraction and API access, using CordformNetworkContainer directly might be a better alternative. Have a look at CordformContainersSpringBootTest for a sample involving Spring Boot’s @DynamicPropertySource annotation.

A simpler example of a CordformNetworkContainer-based RPC test is shown bellow:

/** An RPC-based test using [CordformNetworkContainer] */
@Testcontainers
class CordformNetworkContainerRpcTest {

    companion object {
        @Container
        @JvmStatic
        val nodesContainer = CordformNetworkContainer(
            // A string-based image name, e.g. one of the 
            // NodeImageNameConfig onstants.
            imageName = NodeImageNameConfig.CORDA_OS_ZULU_4_6,
            // Optional, defaults to auto-lookup (build/nodes, ../build/nodes)
            nodesDir = File(System.getProperty("user.dir"))
                 .parentFile.resolve("build/nodes"),
            // Will clone nodesDir to build/testacles/{random UUID}
            // and use that instead
            cloneNodesDir = true,
            privilegedMode = false,
            // Create a Postgres DB for each node (default is H2)
            // The driver will be automatically resolved from 
            // either _{nodeDir}/drivers_ or the classpath
            databaseSettings = CordformDatabaseSettingsFactory.POSTGRES
                 .withTransactionIsolationLevel(READ_COMMITTED))

    }

    @Test
    fun `Can send a yo`() {
        val rpcOps = nodesContainer.getNode("partya")
            .getRpc(/* optional user or username */)
        // Do something...

    }
}