Testing Go CLI Apps

Post by Saul Shanabrook

I recently started using the really great go.cli library to develop a super simple command line server in Go. When testing this server, however, I needed to figure out a way of starting and stopping it on demand. It is very easy to start a CLI app, just call the Run method with a list of arguments.

To stop it, I realized I could use golang.org/x/net/context library, to pass a context into the app and then cancel it after I wanted the test to stop.

First I had to figure out a way create an app dynamically with a context.Context. For some reason, it took me while to figure out I could just wrap making the cli app in a function, that took a context.Context and executed the action using this.

package main

import (  




func makeCliApp(ctx context.Context) *cli.App {  
    app := cli.NewApp()
    app.Name = "subicul"
    app.Usage = "lighting server"

    app.Flags = []cli.Flag{
            Name:   "port",
            Value:  8080,
            Usage:  "TCP port to listen on",
            EnvVar: "SUBICUL_PORT",

    app.Action = func(c *cli.Context) {
        err := websocketserver.MakeStateServer(ctx, c.Int("port"))
        if err != nil {
        // wait here until the context is cancelled
    return app

func main() {  
    app := makeCliApp(context.Background())

Then to test it, I could pass in a custom context and cancel it later, like this (using GoConvey syntax):

package main

import (  


    . "github.com/smartystreets/goconvey/convey"

// TestMainCLI tests that the server starts at a certain
// port and accepts websockets connections on that port.
// It also verifies that the server is stopped after
func TestMainCLI(t *testing.T) {  
    Convey("when I run the app", t, func() {
        port := 9001

        ctx, cancelFunc := context.WithCancel(context.Background())

        app := makeCliApp(ctx)
        go app.Run([]string{"<executable path>", "--port", strconv.Itoa(port)})

        url := fmt.Sprintf("ws://localhost:%v/", port)
        d := websocket.Dialer{}
        Convey("and connect", func() {
            conn, _, err := d.Dial(url, http.Header{})
            So(err, ShouldBeNil)

        Reset(func() {
            So("subicul", ShouldNotBeRunningGoroutines)



You might also notice the use of the ShouldNotBeRunningGoroutines function, which I just blogged about

I am new to Go, so let me know if I am doing anything stupid.

This post was inspired by Peter Bourgon's great talk, which asked the Go community to talk more about their approaches to using the context package.