CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-github-ajalt--clikt-jvm

Multiplatform command line interface parsing for Kotlin

Pending
Overview
Eval results
Files

testing-utilities.mddocs/

Testing Utilities

Test command execution with controlled input/output, environment variables, and terminal settings for comprehensive CLI testing.

Capabilities

CliktCommandTestResult

Data class containing the results of a test execution, including all output streams and exit status.

/**
 * Result of testing a CliktCommand
 * @param stdout Content written to standard output
 * @param stderr Content written to standard error  
 * @param output Combined stdout and stderr content
 * @param statusCode Exit status code (0 for success, non-zero for errors)
 */
data class CliktCommandTestResult(
    val stdout: String,
    val stderr: String,
    val output: String, // stdout + stderr combined
    val statusCode: Int
)

Usage Examples:

class HelloCommand : CliktCommand() {
    private val name by argument()
    override fun run() = echo("Hello $name!")
}

val result = HelloCommand().test("World")
assertEquals("Hello World!\n", result.stdout)
assertEquals("", result.stderr)
assertEquals(0, result.statusCode)

Test Function

Execute a command in a controlled test environment with configurable input, environment, and terminal settings.

/**
 * Test a CliktCommand with controlled environment
 * @param argv Command line arguments as a single string
 * @param stdin Standard input content
 * @param envvars Environment variables to set
 * @param includeSystemEnvvars Include system environment variables
 * @param ansiLevel ANSI color support level
 * @param width Terminal width in characters
 * @param height Terminal height in lines
 * @return CliktCommandTestResult containing all output and status
 */
fun CliktCommand.test(
    argv: String,
    stdin: String = "",
    envvars: Map<String, String> = emptyMap(),
    includeSystemEnvvars: Boolean = false,
    ansiLevel: AnsiLevel = AnsiLevel.NONE,
    width: Int = 79,
    height: Int = 24
): CliktCommandTestResult

fun CliktCommand.test(
    vararg argv: String,
    stdin: String = "",
    envvars: Map<String, String> = emptyMap(),
    includeSystemEnvvars: Boolean = false,
    ansiLevel: AnsiLevel = AnsiLevel.NONE,
    width: Int = 79,
    height: Int = 24
): CliktCommandTestResult

fun CliktCommand.test(
    argv: List<String>,
    stdin: String = "",
    envvars: Map<String, String> = emptyMap(),
    includeSystemEnvvars: Boolean = false,
    ansiLevel: AnsiLevel = AnsiLevel.NONE,
    width: Int = 79,
    height: Int = 24
): CliktCommandTestResult

fun CliktCommand.test(
    argv: Array<String>,
    stdin: String = "",
    envvars: Map<String, String> = emptyMap(),
    includeSystemEnvvars: Boolean = false,
    ansiLevel: AnsiLevel = AnsiLevel.NONE,
    width: Int = 79,
    height: Int = 24
): CliktCommandTestResult

Usage Examples:

class ConfigCommand : CliktCommand() {
    private val config by option("--config", envvar = "CONFIG_FILE")
    override fun run() = echo("Config: $config")
}

// Test with arguments
val result1 = ConfigCommand().test("--config myconfig.yml")
assertEquals("Config: myconfig.yml\n", result1.stdout)

// Test with environment variables
val result2 = ConfigCommand().test("", envvars = mapOf("CONFIG_FILE" to "env.yml"))
assertEquals("Config: env.yml\n", result2.stdout)

// Test with stdin
class ReadCommand : CliktCommand() {
    override fun run() {
        val input = readLine()
        echo("Read: $input")
    }
}

val result3 = ReadCommand().test("", stdin = "test input\n")
assertEquals("Read: test input\n", result3.stdout)

Testing Patterns

Basic Command Testing

Test simple commands with arguments and options:

class GreetCommand : CliktCommand() {
    private val greeting by option("-g", "--greeting").default("Hello")
    private val name by argument()
    
    override fun run() = echo("$greeting $name!")
}

@Test
fun testGreeting() {
    val result = GreetCommand().test("World")
    assertEquals("Hello World!\n", result.stdout)
    assertEquals(0, result.statusCode)
}

@Test
fun testCustomGreeting() {
    val result = GreetCommand().test("--greeting Hi Alice")
    assertEquals("Hi Alice!\n", result.stdout)
}

Error Testing

Test error conditions and validate error messages:

class RequiredOptionCommand : CliktCommand() {
    private val required by option("--required").required()
    override fun run() = echo("Value: $required")
}

@Test
fun testMissingRequired() {
    val result = RequiredOptionCommand().test("")
    assertTrue(result.statusCode != 0)
    assertTrue(result.stderr.contains("Missing option"))
}

Environment Variable Testing

Test environment variable integration:

class EnvCommand : CliktCommand() {
    private val value by option("--value", envvar = "TEST_VALUE")
    override fun run() = echo("Value: $value")
}

@Test
fun testEnvironmentVariable() {
    val result = EnvCommand().test("", envvars = mapOf("TEST_VALUE" to "from-env"))
    assertEquals("Value: from-env\n", result.stdout)
}

Subcommand Testing

Test commands with subcommands:

class MainCommand : CliktCommand() {
    override fun run() = Unit
}

class SubCommand : CliktCommand(name = "sub") {
    private val option by option("--opt")
    override fun run() = echo("Sub: $option")
}

@Test
fun testSubcommand() {
    val main = MainCommand()
    main.subcommands(SubCommand())
    
    val result = main.test("sub --opt value")
    assertEquals("Sub: value\n", result.stdout)
}

Terminal Width Testing

Test help output formatting with different terminal widths:

class HelpCommand : CliktCommand(help = "A command with a very long help description that will wrap at different terminal widths")

@Test
fun testNarrowTerminal() {
    val result = HelpCommand().test("--help", width = 40)
    assertTrue(result.stdout.contains("A command with a very"))
}

@Test
fun testWideTerminal() {
    val result = HelpCommand().test("--help", width = 120)
    assertTrue(result.stdout.contains("A command with a very long help description"))
}

Interactive Input Testing

Test commands that read from stdin:

class InteractiveCommand : CliktCommand() {
    override fun run() {
        echo("Enter name:")
        val name = readLine()
        echo("Hello $name!")
    }
}

@Test
fun testInteractive() {
    val result = InteractiveCommand().test("", stdin = "Alice\n")
    assertTrue(result.stdout.contains("Enter name:"))
    assertTrue(result.stdout.contains("Hello Alice!"))
}

Advanced Testing Features

ANSI Color Testing

Test colored output with different ANSI support levels:

import com.github.ajalt.mordant.rendering.AnsiLevel

@Test
fun testColorOutput() {
    val colorResult = command.test("", ansiLevel = AnsiLevel.TRUECOLOR)
    val noColorResult = command.test("", ansiLevel = AnsiLevel.NONE)
    
    // Colored output will contain ANSI escape sequences
    assertTrue(colorResult.stdout.contains("\u001B["))
    
    // No-color output will not contain escape sequences
    assertFalse(noColorResult.stdout.contains("\u001B["))
}

System Environment Testing

Control whether system environment variables are included:

@Test
fun testSystemEnvExclusion() {
    // Test with only specified environment variables
    val result = command.test("", 
        envvars = mapOf("TEST_VAR" to "test"),
        includeSystemEnvvars = false
    )
    
    // System PATH variable should not be available
    assertFalse(result.stdout.contains(System.getenv("PATH") ?: ""))
}

Best Practices

Test Organization

class CommandTest {
    private lateinit var command: MyCommand
    
    @BeforeEach
    fun setup() {
        command = MyCommand()
    }
    
    @Test
    fun testSuccessCase() {
        val result = command.test("valid arguments")
        assertEquals(0, result.statusCode)
        assertTrue(result.stderr.isEmpty())
    }
    
    @Test
    fun testErrorCase() {
        val result = command.test("invalid arguments")
        assertNotEquals(0, result.statusCode)
        assertTrue(result.stderr.isNotEmpty())
    }
}

Assertion Helpers

fun assertSuccess(result: CliktCommandTestResult) {
    assertEquals(0, result.statusCode, "Command should succeed")
    assertTrue(result.stderr.isEmpty(), "No error output expected")
}

fun assertError(result: CliktCommandTestResult, expectedMessage: String) {
    assertNotEquals(0, result.statusCode, "Command should fail")
    assertTrue(result.stderr.contains(expectedMessage), 
        "Expected error message not found: $expectedMessage")
}

The testing utilities provide comprehensive support for testing CLI applications with full control over the execution environment, making it easy to verify command behavior, error handling, and output formatting across different scenarios.

Install with Tessl CLI

npx tessl i tessl/maven-com-github-ajalt--clikt-jvm

docs

arguments.md

configuration-sources.md

core-commands.md

exceptions.md

index.md

options.md

parameter-groups.md

parameter-types.md

shell-completion.md

testing-utilities.md

tile.json