/keylargo

A minimal Aruba-like experience for godog

Primary LanguageGoApache License 2.0Apache-2.0

Keylargo is a set of godog helpers for in-process CLI application testing. In essence, it is a minimal Aruba-like experience.

Why?

Because I work on a lot of CLI apps, and I drank the Cucumber/Aruba special punch to such a degree that it's my preferred way to work.

Also, since it is an in-process tester, it's fairly trivial to mock external REST APIs and the like (for better or for worse).

Installation

I suggest the use of dep for managing your Go deps, but you should be able to install it directly without issues:

go get github.com/ess/keylargo

As mentioned, though, it's probably better to use a package manager that supports SemVer.

Usage

Let's talk about the echo command for a second. By default, it just prints its arguments to STDOUT, followed by a newline. If you provide the -n flag, it still does that, but it does not print the newline.

The Command

In order for keylargo to run a command, it must implement the keylargo.Command interface:

type Command interface {
	SetArgs([]string)
	Execute() error
}

You can use any CLI framework you like (or just roll your own implementation), but a great example of an object that already implements this interface is *cobra.Command from cobra.

Also, in order for keylargo to actually run your command, you'll need to set it as the root command via keylargo.SetRootCmd(yourCommand), most likely in your godog suite setup (see main_test.go below).

Finally, it is worth pointing out that this whole thing falls apart if the command (or any of its dependencies) should call os.Exit() (or more or less anything else that would normally cause program termination). Instead, it's expected that the command will bubble any and all errors up to the top level.

Keylargo Steps

Keylargo adds the following steps to the suite:

  • When I run "command and its args" is used to actually run the command
  • Then the command succeeds is used to indicate that we expect the command to return no error
  • Then the command fails is used to indicate that we expect the command to return an error

Keylargo Helpers

Keylargo containst he following helper functions that can be used to compose your own steps:

  • keylargo.LastCommandOutput() is a string that contains the captured output for the last command ran via the When I run step.

features/echo.feature

Feature: echo
  In order to print stuff to the terminal
  As a user
  I want to be able to echo some text
  And I want for this stanza to be less awkward, but that's what examples are like.

  Scenario: default behavior
    When I run "echo ohai tharrrrrrr"
    Then the output contains "ohai tharrrrrrr"
    And the output ends with a newline
    And the command succeeds

  Scenario: suppress newline
    When I run "echo -n sup duder"
    Then the output contains "sup duder"
    But the output has no newlines
    And the command succeeds

main_test.go

package main

import (
	"fmt"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/DATA-DOG/godog"
	"github.com/ess/keylargo"
)

func theOutputContains(expected string) error {
	if !strings.Contains(keylargo.LastCommandOutput(), expected) {
		return fmt.Errorf("Expected output to contain '%s'", expected)
	}

	return nil
}

func terminatingNewline() error {
	if !strings.HasSuffix(keylargo.LastCommandOutput(), "\n") {
		return fmt.Errorf("Expected output to have a newline terminator")
	}

	return nil
}

func noNewline() error {
	if strings.Contains(keylargo.LastCommandOutput(), "\n") {
		return fmt.Errorf("Expected output to contain no newlines")
	}

	return nil
}

func TestMain(m *testing.M) {
	// Set up the command that is actually run via keylargo. The object passed in
	// must implement the following interface:
	//
	//   type Command interface{
	//     SetArgs([]string)
	//     Execute() error
	//   }

	keylargo.SetRootCmd(echoCmd)

	status := godog.RunWithOptions("godog",
		func(s *godog.Suite) {
			// Add the keylargo steps to the suite
			keylargo.StepUp(s)

			// Add your own custom steps

			s.Step(`^the output contains "([^"]*)"$`, theOutputContains)
			s.Step(`the output ends with a newline`, terminatingNewline)
			s.Step(`the output has no newlines`, noNewline)

			s.BeforeScenario(func(interface{}) {
				skipNewline = false
			})

		},

		godog.Options{
			Format:    "pretty",
			Paths:     []string{"features"},
			Randomize: time.Now().UTC().UnixNano(),
		},
	)

	if st := m.Run(); st > status {
		status = st
	}

	os.Exit(status)
}

main.go

package main

import (
	"fmt"
	"os"
	"strings"

	"github.com/spf13/cobra"
)

var skipNewline bool

var echoCmd = &cobra.Command{
	Use:   "echo",
	Short: "what did i just say?",
	RunE: func(cmd *cobra.Command, args []string) error {
		output := strings.Join(args, " ")

		fmt.Printf("%s"+terminator(), output)

		return nil
	},
}

func init() {
	echoCmd.Flags().BoolVarP(&skipNewline, "no-newline", "n", false,
		"Suppress the terminating newline")
}

func main() {
	err := echoCmd.Execute()
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

func terminator() string {
	if skipNewline {
		return ""
	}

	return "\n"
}

History

  • v0.1.0 - No hard godog dependencies
  • v0.0.1 - Initial release