Test
Solidblocks infra-test is an infrastructure testing library for writing unit tests in Java/Kotlin to test actual the state of your servers.
Usage
To use solidblocks-test just add the dependency to your Gradle or Maven build
build.gradle.kts
plugins {
id("org.jetbrains.kotlin.jvm") version "2.2.10"
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.junit.jupiter:junit-jupiter-api:5.11.0")
implementation("org.junit.jupiter:junit-jupiter-engine:5.11.0")
implementation("de.solidblocks:infra-test:v0.4.10-rc1")
}
tasks.named<Test>("test") {
useJUnitPlatform()
}Depending on your use case you can either inject the test context using the SolidblocksTest extension
extension usage
package solidblocks.test.gradle
import de.solidblocks.infra.test.SolidblocksTest
import de.solidblocks.infra.test.SolidblocksTestContext
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@ExtendWith(SolidblocksTest::class)
class ExtensionUsage {
@Test
fun extensionUsage(testContext: SolidblocksTestContext) {
val localTestContext = testContext.local()
// localTestContext.command(...)
}
}or use the factory methods to create the test context directly
factory method usage
package solidblocks.test.gradle
import localTestContext
import org.junit.jupiter.api.Test
class FactoryMethodUsage {
@Test
fun factoryMethodUsage() {
val localTestContext = localTestContext()
// localTestContext.command(...)
}
}The extension automatically manages the lifecycle of created resources and cleans up everything when the test is finished.
Logging
All produced log output is marked with its origin, to make it easy to distinguish it from other logs during a test run. The following snippets contains an example log with some log statements form the library itself [INFO], logs from forked commands [STDOUT] and logs from a cloud-init run [CLOUDINIT]
[ INFO] Terraform checksums already downloaded at '/home/pelle/git/solidblocks/solidblocks-test/.cache/terraform_1.14.2_SHA256SUMS'
[ INFO] Terraform already downloaded at '/home/pelle/git/solidblocks/solidblocks-test/.cache/terraform_1.14.2_linux_amd64.zip'
[ INFO] Extracting Terraform binary to '/home/pelle/git/solidblocks/solidblocks-test/.bin/1.14.2/terraform'
[ INFO] Terraform installed at '/home/pelle/git/solidblocks/solidblocks-test/.bin/1.14.2/terraform'
[ INFO] running '/home/pelle/git/solidblocks/solidblocks-test/.bin/1.14.2/terraform init -upgrade' in '/home/pelle/git/solidblocks/solidblocks-test/build/resources/test/terraformCloudInitTestBed1'
[ STDOUT] Initializing the backend...
[ STDOUT] Initializing provider plugins...
[ STDOUT] - Finding hetznercloud/hcloud versions matching ">= 1.48.0"...
[ STDOUT] - Finding hashicorp/random versions matching "3.7.2"...
[ STDOUT] - Finding hashicorp/tls versions matching "4.1.0"...
[ STDOUT] - Using previously-installed hashicorp/tls v4.1.0
[ STDOUT] - Using previously-installed hetznercloud/hcloud v1.57.0
[ STDOUT] - Using previously-installed hashicorp/random v3.7.2
...
[ STDOUT] private_key_openssh_ecdsa = <sensitive>
[ STDOUT] private_key_openssh_ed25519 = <sensitive>
[ STDOUT] private_key_openssh_rsa = <sensitive>
[ STDOUT] private_key_pem_ecdsa = <sensitive>
[ STDOUT] private_key_pem_ed25519 = <sensitive>
[ STDOUT] private_key_pem_rsa = <sensitive>
[CLOUDINIT] Cloud-init v. 22.4.2 running 'init-local' at Wed, 24 Dec 2025 13:27:44 +0000. Up 8.65 seconds.
[CLOUDINIT] Cloud-init v. 22.4.2 running 'init' at Wed, 24 Dec 2025 13:27:48 +0000. Up 11.98 seconds.
...
[CLOUDINIT] ci-info: +-------+-------------------------+---------+-----------+-------+
[CLOUDINIT] ci-info: | Route | Destination | Gateway | Interface | Flags |
[CLOUDINIT] ci-info: +-------+-------------------------+---------+-----------+-------+
[CLOUDINIT] ci-info: | 0 | 2a01:4f9:c010:957b::/64 | :: | eth0 | U |
[CLOUDINIT] ci-info: | 1 | fe80::/64 | :: | eth0 | U |
[CLOUDINIT] ci-info: | 2 | ::/0 | fe80::1 | eth0 | UG |
[CLOUDINIT] ci-info: | 4 | local | :: | eth0 | U |
[CLOUDINIT] ci-info: | 5 | local | :: | eth0 | U |
[CLOUDINIT] ci-info: | 6 | multicast | :: | eth0 | U |
[CLOUDINIT] ci-info: +-------+-------------------------+---------+-----------+-------+Test Contexts
Test contexts can be used to test and assert different infrastructure related properties and behaviors.
Local and docker command context
The local and docker command contexts allow to run commands and scrips on the machine executing the Junit tests and asserts the outcomes.
Run command or script
package solidblocks.test.gradle.command
import de.solidblocks.infra.test.SolidblocksTest
import de.solidblocks.infra.test.SolidblocksTestContext
import de.solidblocks.infra.test.assertions.shouldHaveExitCode
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@ExtendWith(SolidblocksTest::class)
class LocalCommandContext {
@Test
fun localCommandContext(testContext: SolidblocksTestContext) {
val result = testContext.local().command("whoami").runResult()
result shouldHaveExitCode 0
}
}Run command or script inside Docker
package solidblocks.test.gradle.command
import de.solidblocks.infra.test.SolidblocksTest
import de.solidblocks.infra.test.SolidblocksTestContext
import de.solidblocks.infra.test.assertions.shouldHaveExitCode
import de.solidblocks.infra.test.docker.DockerTestImage
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@ExtendWith(SolidblocksTest::class)
class DockerCommandContext {
@Test
fun dockerCommandContext(testContext: SolidblocksTestContext) {
val result = testContext.docker(DockerTestImage.UBUNTU_22).command("whoami").runResult()
result shouldHaveExitCode 0
}
}Options
Both the local and the docker command execution can be configured before the command is executed
package solidblocks.test.gradle.command
import de.solidblocks.infra.test.SolidblocksTest
import de.solidblocks.infra.test.SolidblocksTestContext
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import java.nio.file.Path
import kotlin.time.Duration.Companion.seconds
@ExtendWith(SolidblocksTest::class)
class LocalCommandOptions {
@Test
fun localCommandContext(testContext: SolidblocksTestContext) {
val command = testContext.local().command("whoami")
// timeout for running the command
command.timeout(10.seconds)
// inherit environment variables from the shell that spawned the units tests
command.inheritEnv(true)
// set working directory for command execution
command.workingDir(Path.of("/tmp"))
// set environment variable for command
command.env("ENV_VAR1" to "foo-bar")
// command.runResult()
}
}Assertions
The following assertions are available for the results of local().command(...).runResult() as well as docker(DockerTestImage.UBUNTU_22).command(...).runResult()
package solidblocks.test.gradle.command
import de.solidblocks.infra.test.SolidblocksTest
import de.solidblocks.infra.test.SolidblocksTestContext
import de.solidblocks.infra.test.assertions.*
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
@ExtendWith(SolidblocksTest::class)
class CommandAssertions {
@Test
fun commandAssertions(testContext: SolidblocksTestContext) {
val result = testContext.local().command("whoami").runResult()
result shouldHaveExitCode 0
result outputShouldBe "something"
result stderrShouldBe "something"
result stderrShouldMatch ".*something.*"
result stdoutShouldMatch ".*something.*"
result.stdoutShouldBeEmpty()
result.stderrShouldBeEmpty()
result runtimeShouldBeGreaterThan 10.milliseconds
result runtimeShouldBeLessThan 5.seconds
}
}Terraform
The terraform context allows to apply Terraform configurations from within the test code. It exposes the full init(), apply() and destroy() lifecycle that can be used either to test Terraform modules, or to prepare an environment for other tests.
When used the test context as well as the factory methods will always download the correct version for the architecture executing the tests, supporting Linux, MacOS and Windows on x86 and arm.
Test a Terraform config
Given a folder containing Terraform files, like for example
/module1/main.tf
resource "random_string" "string1" {
length = 12
}
output "string1" {
value = random_string.string1.id
}The resources described in this file can be created using the Terraform test context
Test a terraform setup
package solidblocks.test.gradle.terraform
import de.solidblocks.infra.test.SolidblocksTest
import de.solidblocks.infra.test.SolidblocksTestContext
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@ExtendWith(SolidblocksTest::class)
public class TerraformContext {
@Test
fun terraformExtension(context: SolidblocksTestContext) {
val modulePath = TerraformContext::class.java.getResource("/module").path
val terraform = context.terraform(modulePath)
terraform.init()
terraform.apply()
// destroy will be called automatically when all tests from the class are finished
}
}Options
package solidblocks.test.gradle.terraform
import de.solidblocks.infra.test.SolidblocksTest
import de.solidblocks.infra.test.SolidblocksTestContext
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@ExtendWith(SolidblocksTest::class)
public class TerraformOptions {
@Test
fun terraformExtension(context: SolidblocksTestContext) {
val modulePath = TerraformOptions::class.java.getResource("/module").path
// use a specific Terraform version
val terraform = context.terraform(modulePath, "1.14.1")
// set terraform variable (implicitly sets the
// environment variable 'TF_VAR_variable1'
terraform.addVariable("variable1", "foo-bar")
// do not clean up any resources when the test run fails,
// e.g. destroy will not be executed after test run is finished
context.cleanupAfterTestFailure(false)
}
}Host
The host context allows to verify properties of remote hosts.
package solidblocks.test.gradle.host
import de.solidblocks.infra.test.SolidblocksTest
import de.solidblocks.infra.test.SolidblocksTestContext
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@ExtendWith(SolidblocksTest::class)
class HostContext {
@Test
fun commandAssertions(testContext: SolidblocksTestContext) {
val host = testContext.host("pelle.io")
}
}Assertions
package solidblocks.test.gradle.host
import de.solidblocks.infra.test.SolidblocksTest
import de.solidblocks.infra.test.SolidblocksTestContext
import de.solidblocks.infra.test.assertions.portShouldBeClosed
import de.solidblocks.infra.test.assertions.portShouldBeOpen
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@ExtendWith(SolidblocksTest::class)
class HostAssertions {
@Test
fun commandAssertions(testContext: SolidblocksTestContext) {
val host = testContext.host("pelle.io")
host portShouldBeOpen 22
host portShouldBeClosed 80
}
}SSH
The host ssh context allows assertions to run on remote hosts. Prerequisites are a running SSH server o the host provided by <ssh_host> along with a matching <private_key>. The private key can be an pem or openssh encoded rsa, ed25519 or ecdsa key.
package solidblocks.test.gradle.ssh
import de.solidblocks.infra.test.SolidblocksTest
import de.solidblocks.infra.test.SolidblocksTestContext
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@ExtendWith(SolidblocksTest::class)
public class SSHContext {
@Test
fun terraformExtension(context: SolidblocksTestContext) {
val ssh = context.ssh("<ssh_host>", "<private_key>")
val result = ssh.command("...")
}
}Assertions
SSHContext.command(...) supports the same assertions as local().command(...).runResult() and docker(...).command(...).runResult().
Cloud-Init
The cloud-init context allows assertions based on the artifacts generated after a cloud-init run. Like in the SSH context, the connection to the machine is created via SSH, so the same prerequisites as for the SSH context apply.
package solidblocks.test.gradle.cloudinit
import de.solidblocks.infra.test.SolidblocksTest
import de.solidblocks.infra.test.SolidblocksTestContext
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@ExtendWith(SolidblocksTest::class)
public class CloudInitContext {
@Test
fun terraformExtension(context: SolidblocksTestContext) {
val cloudInit = context.cloudInit("<ssh_host>", "<private_key>")
// print the output of '/var/log/cloud-init-output.log' in case a
// test fails. This is disabled by default to avoid accidental leakage
// of secrets that may be processed during cloud-init runs
cloudInit.printOutputLogOnTestFailure()
}
}