I'm starting a new OSS CLI tool that utilizes spf13/cobra. Being new to golang, I'm having a hard time figuring out the best way to test commands in isolation. Can anybody give me an example of how to test a command? Couple of caveats:
get_test.go
in the cmd directory...which I was under the impression was the golang best practice.Please correct me if I'm wrong.
Here's the cmd I'm trying to test: https://github.com/sahellebusch/raider/blob/3-add-get-alerts/cmd/get.go.
Open to ideas, suggestions, criticisms, whatever.
Go is a particularly good language for CLI's I have found. At least compared to Java/C#/Python. It's reasonably fast, compiles down to a simple to distribute binary, and the language is forgiving enough that you can do exploratory programming in it.
At the command line in the greetings directory, run the go test command to execute the test. The go test command executes test functions (whose names begin with Test ) in test files (whose names end with _test.go). You can add the -v flag to get verbose output that lists all of the tests and their results.
There are multiple approaches to implementing a CLI using go. This is the basic structure of the CLI I developed which is mostly influenced by the docker CLI and I have added unit tests as well.
The first thing you need is to have CLI as an interface. This will be inside a package named "cli".
package cli
type Cli interface {
// Have interface functions here
sayHello() error
}
This will be implemented by 2 clis: HelloCli (Our real CLI) and MockCli (used for unit tests)
package cli
type HelloCli struct {
}
func NewHelloCli() *HelloCli {
cli := &HelloCli{
}
return cli
}
Here the HelloCli will implement sayHello function as follows.
package cli
func (cli *HelloCli) SayHello() error {
// Implement here
}
Similarly, there will be a mock cli in a package named test
that would implement cli interface and it will also implement the sayHello function.
package test
type MockCli struct {
}
func NewMockCli() *HelloCli {
cli := &MockCli{
}
return cli
}
func (cli *MockCli) SayHello() error {
// Mock implementation here
}
Now I will show how the command is added. First I would have the main package and this is where I would add all the new commands.
package main
func newCliCommand(cli cli.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "foo <command>"
}
cmd.AddCommand(
newHelloCommand(cli),
)
return cmd
}
func main() {
helloCli := cli.NewHelloCli()
cmd := newCliCommand(helloCli)
if err := cmd.Execute(); err != nil {
// Do something here if execution fails
}
}
func newHelloCommand(cli cli.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "hello",
Short: "Prints hello",
Run: func(cmd *cobra.Command, args []string) {
if err := pkg.RunHello(cli, args[0]); err != nil {
// Do something if command fails
}
},
Example: " foo hello",
}
return cmd
}
Here, I have one command called hello
. Next, I will have the implementation in a separate package called "pkg".
package pkg
func RunHello(cli cli.Cli) error {
// Do something in this function
cli.SayHello()
return nil
}
The unit tests will also be contained in this package in a file named hello_test
.
package pkg
func TestRunHello(t *testing.T) {
mockCli := test.NewMockCli()
tests := []struct {
name string
}{
{
name: "my test 1",
},
{
name: "my test 2"
},
}
for _, tst := range tests {
t.Run(tst.name, func(t *testing.T) {
err := SayHello(mockCli)
if err != nil {
t.Errorf("error in SayHello, %v", err)
}
})
}
}
When you execute foo hello
, the HelloCli
will be passed to the sayHello() function and when you run unit tests, MockCli
will be passed.
You can check how cobra
itself does it - https://github.com/spf13/cobra/blob/master/command_test.go
Basically you can refactor the actual Command logic(the run function) into a separate function and test that function. You probably want to name your functions properly instead of just calling it run
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With