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.
init
function and global variables there at all? – Subsidiary