Skip to content

Commit

Permalink
Test coverage (#9)
Browse files Browse the repository at this point in the history
* Add unit tests for CommandLineApplication
* Adds test cases for command execution and introduces the `CaptureStdout` function to capture standard output for a given callback during tests.
* Added test cases for commands of the example apps to validate their execution (to gain coverage through integration tests).
  • Loading branch information
matzefriedrich authored Sep 26, 2024
1 parent 30fc209 commit 23825a2
Show file tree
Hide file tree
Showing 12 changed files with 256 additions and 4 deletions.
9 changes: 8 additions & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,11 @@ jobs:

- name: Run tests with coverage
run: go test -v -race -covermode atomic -coverprofile=covprofile -coverpkg ./... ./...


- name: Install goveralls
run: go install github.com/mattn/goveralls@latest

- name: Send coverage report
env:
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: goveralls -coverprofile=covprofile -service=github
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

# Output of the go coverage tool, specifically when used with LiteIDE
*.out
covprofile

# Dependency directories (remove the comment below to include it)
vendor/
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[![CI](https://github.com/matzefriedrich/cobra-extensions/actions/workflows/go.yml/badge.svg)](https://github.com/matzefriedrich/cobra-extensions/actions/workflows/go.yml)
[![Coverage Status](https://coveralls.io/repos/github/matzefriedrich/cobra-extensions/badge.svg)](https://coveralls.io/github/matzefriedrich/cobra-extensions)
[![Go Reference](https://pkg.go.dev/badge/github.com/matzefriedrich/cobra-extensions.svg)](https://pkg.go.dev/github.com/matzefriedrich/cobra-extensions)
[![Go Report Card](https://goreportcard.com/badge/github.com/matzefriedrich/cobra-extensions)](https://goreportcard.com/report/github.com/matzefriedrich/cobra-extensions)
![License](https://img.shields.io/github/license/matzefriedrich/cobra-extensions)
Expand Down
28 changes: 28 additions & 0 deletions example/commands/decrypt_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package commands

import (
"github.com/matzefriedrich/cobra-extensions/internal/utils"
"github.com/stretchr/testify/assert"
"testing"
)

func Test_DecryptCommand_Execute(t *testing.T) {

// Arrange
const expectedMessage = "Hello World"

encryptedMessage, _ := utils.CaptureStdout(t, "", func() error {
encryptCommand := CreateEncryptMessageCommand()
encryptCommand.SetArgs([]string{"--message", expectedMessage})
return encryptCommand.Execute()
})

decryptedMessage, err := utils.CaptureStdout(t, encryptedMessage, func() error {
decryptCommand := CreateDecryptMessageCommand()
return decryptCommand.Execute()
})

// Assert
assert.NoError(t, err)
assert.Equal(t, expectedMessage, decryptedMessage)
}
18 changes: 18 additions & 0 deletions example/commands/encrypt_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package commands

import (
"github.com/stretchr/testify/assert"
"testing"
)

func Test_CryptCommand_Execute(t *testing.T) {

// Arrange
sut := CreateEncryptMessageCommand()
sut.SetArgs([]string{"--message", "Hello World"})
// Act
err := sut.Execute()

// Assert
assert.NoError(t, err)
}
18 changes: 18 additions & 0 deletions example/commands/hello_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package commands

import (
"github.com/stretchr/testify/assert"
"testing"
)

func Test_HelloCommand_Execute(t *testing.T) {
// Arrange
sut := CreateHelloCommand()
sut.SetArgs([]string{"John Doe"})

// Act
err := sut.Execute()

// Assert
assert.NoError(t, err)
}
58 changes: 58 additions & 0 deletions internal/utils/capture_stdout.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package utils

import (
"bytes"
"os"
"testing"
)

// CaptureStdout captures the standard output of the provided callback function.
//
// Parameters:
// - t: the testing framework instance.
// - in: the input string to be written to stdin.
// - cb: the callback function whose stdout needs to be captured.
//
// Returns:
// - The captured stdout as a string.
func CaptureStdout(t *testing.T, in string, cb func() error) (string, error) {

originalStdin := os.Stdin
defer func() { os.Stdin = originalStdin }()

originalStdOut := os.Stdout
defer func() { os.Stdout = originalStdOut }()

stdinReader, stdinWriter, stdinPipeErr := os.Pipe()
if stdinPipeErr != nil {
t.Fatalf("failed to create pipe: %v", stdinPipeErr)
}

stdoutReader, stdoutWriter, stdoutPipeErr := os.Pipe()
if stdoutPipeErr != nil {
t.Fatalf("failed to create pipe: %v", stdoutPipeErr)
}

os.Stdin = stdinReader
os.Stdout = stdoutWriter

_, _ = stdinWriter.Write([]byte(in))
_ = stdinWriter.Close()

output := make(chan string)
go func() {
var buf bytes.Buffer
_, _ = buf.ReadFrom(stdoutReader)
output <- buf.String()
}()

err := cb()
if err != nil {
return "", err
}

_ = stdoutWriter.Close()

data := <-output
return data, nil
}
4 changes: 2 additions & 2 deletions pkg/charmer/command_line_application.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ func (a *CommandLineApplication) AddCommand(c ...*cobra.Command) *CommandLineApp
func (a *CommandLineApplication) AddGroupCommand(c *cobra.Command, setup types.CommandsSetupFunc) *CommandLineApplication {
a.root.AddCommand(c)
if setup != nil {
wrapper := commandSetup{command: c}
setup(&wrapper)
wrapper := newCommandSetup(c)
setup(wrapper)
}
return a
}
87 changes: 87 additions & 0 deletions pkg/charmer/command_line_application_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package charmer

import (
"github.com/matzefriedrich/cobra-extensions/pkg/commands"
"github.com/matzefriedrich/cobra-extensions/pkg/types"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"testing"
)

func Test_CommandLineApplication_Execute_without_any_command_does_not_return_error(t *testing.T) {

// Arrange
sut := NewCommandLineApplication("test-app", "")

// Act
err := sut.Execute()

// Assert
assert.NoError(t, err)
}

func Test_CommandLineApplication_Execute_smoke_test(t *testing.T) {

// Arrange
application := NewCommandLineApplication("test-app", "")

command := newTestCommand(noop())
sut := command.AsTypedCommand()
application.AddGroupCommand(newTestGroupCommand(), func(setup types.CommandSetup) {
setup.AddCommand(sut)
})

application.root.SetArgs([]string{"group", "test"})

// Act
err := application.Execute()

// Assert
assert.NoError(t, err)
assert.True(t, command.Executed())
}

type executeFunc func()

func noop() executeFunc {
return func() {}
}

type testCommand struct {
use types.CommandName `flag:"test"`
typedCommand types.TypedCommand
executeFunc executeFunc
executed bool
}

func (t *testCommand) Executed() bool {
return t.executed
}

func (t *testCommand) Execute() {
t.executed = true
t.executeFunc()
}

var _ types.TypedCommand = (*testCommand)(nil)

func newTestCommand(execute executeFunc) *testCommand {
return &testCommand{
executeFunc: execute,
}
}

func (t *testCommand) AsTypedCommand() *cobra.Command {
typedCommand := commands.CreateTypedCommand(t)
return typedCommand
}

type testGroupCommand struct {
types.BaseCommand
use types.CommandName `flag:"group"`
}

func newTestGroupCommand() *cobra.Command {
command := &testGroupCommand{BaseCommand: types.BaseCommand{}}
return commands.CreateTypedCommand(command)
}
5 changes: 5 additions & 0 deletions pkg/charmer/command_setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ type commandSetup struct {

var _ types.CommandSetup = &commandSetup{}

// newCommandSetup initializes a new commandSetup instance with the provided Cobra command object.
func newCommandSetup(command *cobra.Command) *commandSetup {
return &commandSetup{command: command}
}

// AddCommand adds one or more sub-commands to the current command.
func (w *commandSetup) AddCommand(c ...*cobra.Command) types.CommandSetup {
w.command.AddCommand(c...)
Expand Down
29 changes: 29 additions & 0 deletions pkg/charmer/command_setup_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package charmer

import (
"github.com/matzefriedrich/cobra-extensions/pkg/types"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"slices"
"testing"
)

func Test_CommandSetup_AddGroupCommand_adds_subcommand_via_setup_func(t *testing.T) {

// Arrange
root := &cobra.Command{}
sut := newCommandSetup(root)

// Act
group := &cobra.Command{Use: "group"}
c := &cobra.Command{Use: "command"}

sut.AddGroupCommand(group, func(setup types.CommandSetup) {
setup.AddCommand(c)
})

actual := slices.Contains(group.Commands(), c)

// Assert
assert.True(t, actual)
}
2 changes: 1 addition & 1 deletion pkg/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ type TypedCommand interface {
}

// CommandsSetupFunc defines a function type used to set up commands within a CommandSetup context.
type CommandsSetupFunc func(w CommandSetup)
type CommandsSetupFunc func(setup CommandSetup)

// CommandSetup provides methods to add and organize commands within a command-line application interface.
type CommandSetup interface {
Expand Down

0 comments on commit 23825a2

Please sign in to comment.