Multiplatform command line interface parsing for Kotlin
—
Test command execution with controlled input/output, environment variables, and terminal settings for comprehensive CLI testing.
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)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
): CliktCommandTestResultUsage 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)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)
}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"))
}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)
}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)
}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"))
}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!"))
}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["))
}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") ?: ""))
}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())
}
}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