Integralist
Integralist

Reputation: 6185

Unable to test a Golang CLI tool's output

I have a cli tool written in Go which produces the following output:

  Command: config get
      Env: int
Component: foo-component

Unable to find any configuration within Cosmos (http://api.foo.com) for foo-component.

I would like to verify this output within a test.

The test I have written (and doesn't pass) is as follows:

package command

import (
    "testing"

    "github.com/my/package/foo"
)

type FakeCliContext struct{}

func (s FakeCliContext) String(name string) string {
    return "foobar"
}

func ExampleInvalidComponentReturnsError() {
    fakeBaseURL := "http://api.foo.com"
    fakeCliContext := &FakeCliContext{}
    fakeFetchFlag := func(foo.CliContext) (map[string]string, error) {
        return map[string]string{
            "env":       "int",
            "component": "foo-component",
        }, nil
    }

    GetConfig(*fakeCliContext, fakeFetchFlag, fakeBaseURL)

    // Output:
    //   Command: config get
    //       Env: int
    // Component: foo-component
    //
    // Unable to find any configuration within Cosmos (http://api.foo.com) for foo-component.
}

The majority of the code is creating fake objects that I'm injecting into my function call GetConfig.

Effectively there is no return value from GetConfig only a side effect of text being printed to stdout.

So I'm using the Example<NameOfTest> format to try and verify the output.

But all I just back when I run go test -v is:

=== RUN   ExampleInvalidComponentReturnsError
exit status 1
FAIL    github.com/my/package/thing 0.418s

Does anyone know what I might be missing?

I've found that if I add an additional test after the 'Example' one above, for example called Test<NameOfTest> (but consistenting of effectively the same code), then this will actually display the function's output into my stdout when running the test:

func TestInvalidComponentReturnsError(t *testing.T) {
    fakeBaseURL := "http://api.foo.com"
    fakeCliContext := &FakeCliContext{}
    fakeFetchFlag := func(utils.CliContext) (map[string]string, error) {
        return map[string]string{
            "env":       "int",
            "component": "foo-component",
        }, nil
    }

    GetConfig(*fakeCliContext, fakeFetchFlag, fakeBaseURL)
}

The above example test will now show the following output when executing go test -v:

=== RUN   TestInvalidComponentReturnsError
  Command: config get
      Env: int
Component: foo-component

Unable to find any configuration within Cosmos (http://api.foo.com) for foo-component.
exit status 1
FAIL    github.com/bbc/apollo/command   0.938s

Upvotes: 0

Views: 360

Answers (1)

Integralist
Integralist

Reputation: 6185

OK so the solution to this problem was part architecture and part removal/refactor of code.

  1. I extracted the private functions from the cli command package so they became public functions in a separate function

  2. I refactored the code so that all dependencies were injected, this then allowed me to mock these objects and verify the the expected methods were called

  3. Now the private functions are in a package and made public, I'm able to test those things specifically, outside of the cli context

  4. Finally, I removed the use of os.Exit as that was a nightmare to deal with and wasn't really necessary

Upvotes: 0

Related Questions