# Testing Commands

> Run Commands in tests with clitest, check their results and output, answer their prompts by flag name, and keep their signatures stable.

This guide covers testing Commands. You will learn how to:

- Run a Command in a test and check its output
- Check its JSON result
- Answer its prompts
- Set environment variables for it
- Keep every Command's signature stable

## Overview

The `clitest` package (`github.com/mucolabs/tinka/cli/clitest`) runs Commands in a test, in the same process, the way `act` runs them. A run sees no terminal and none of the process's environment, only what the test gives it, so a test behaves the same on your machine and in CI.

## Run a Command

Pass the test and the root of the Commands to `clitest.New`, then `Run` the Command with its arguments and flags. `app.Commands()` returns the root of the App's Commands, Tinka's own included. Build the App in a function that `main` and the tests share:

```go title="main.go"
package main

import (
	"os"

	"github.com/mucolabs/tinka"
)

func main() {
	os.Exit(newApp().Main(os.Args[1:]))
}

// newApp builds the App with its routes, providers and Commands.
func newApp() *tinka.App {
	app := tinka.New(tinka.HTTPConfig{})
	app.Command(greet, publishCommand(app))
	return app
}
```

```go title="commands_test.go"
package main

import (
	"testing"

	"github.com/mucolabs/tinka/cli/clitest"
)

func TestGreet(t *testing.T) {
	r := clitest.New(t, newApp().Commands()).Run("greet", "Bea")
	if r.Code != 0 || r.Stdout != "Hello, Bea!\n" {
		t.Errorf("exit %d, stdout %q, stderr %q", r.Code, r.Stdout, r.Stderr)
	}
}
```

::::options

:::option{name="Code" type="int"}
The exit status: `0` on success, `1` on failure, `2` for a usage error.
:::

:::option{name="Stdout" type="string"}
The result, as people see it, or the JSON result with `--json`.
:::

:::option{name="Stderr" type="string"}
Progress, status lines and errors.
:::

::::

## Check the JSON result

Pass `--json`, and decode the result with `Decode`:

```go
	r := clitest.New(t, newApp().Commands()).Run("posts:publish", "--limit", "5", "--json")
	var res struct {
		Result string `json:"result"`
		Count  int    `json:"count"`
	}
	if err := r.Decode(&res); err != nil || res.Result != "ok" {
		t.Fatalf("%v: %s", err, r.Stdout)
	}
```

## Answer prompts

Without answers, a run has nobody to answer, as in CI: prompts return their flags' defaults, and required ones fail. That's often the simplest test, since passing the flags answers every prompt.

To test the prompts themselves, answer each one with `Answer`, by the name of its flag, or of the argument for a missing argument's prompt. Rewording a question then never breaks a test:

```go
func TestMakeController(t *testing.T) {
	r := clitest.New(t, newApp().Commands()).
		Answer("name", "posts").
		Answer("title", "Hello").
		Answer("db", "postgres").
		Answer("resource", "y").
		Run("make:controller")
	if r.Code != 0 {
		t.Fatalf("exit %d: %s", r.Code, r.Stderr)
	}
}
```

Answer as a person would type: text, `y` or `n` for a confirmation, a choice's value for a selection, and an empty string for the default. A prompt the test didn't answer fails the run, and an answer no prompt used fails the test, so the test always matches the questions the Command asks. `Terminal()` runs with a person present and no answers, to check that a Command doesn't prompt at all.

## Set environment variables

`Env` sets an environment variable for the runs, such as `APP_ENV` for a [destructive Command](../command-line/commands.md#guard-commands-that-change-data):

```go
	r := clitest.New(t, newApp().Commands()).Env("APP_ENV", "production").Run("db:wipe")
	if r.Code != 2 {
		t.Errorf("db:wipe must need --force in production, got exit %d", r.Code)
	}
```

## Keep signatures stable

A Command's name, arguments and flags are an interface that scripts and agents depend on. `clitest.Signatures` checks every Command's signature, as `--help --json` describes it, against a file, so a change to one is always deliberate:

```go
func TestSignatures(t *testing.T) {
	clitest.Signatures(t, newApp().Commands(), "testdata/commands.golden")
}
```

Run the test with `TINKA_UPDATE=1` to write the file, and again after a change you meant, then commit it:

```sh
TINKA_UPDATE=1 go test -run TestSignatures .
```

See also: [Testing introduction](introduction.md), [Creating Commands](../command-line/commands.md), [Prompts](../command-line/prompts.md)
