How to use gmang "fmt" library to test CLI

Disclaimer: I wish you a merry XMas and I hope that my question does not bother you!

sample.go:

package main import( "fmt" "os" ) type sample struct { value int64 } func (s sample) useful() { if s.value == 0 { fmt.Println("Error: something is wrong!") os.Exit(1) } else { fmt.Println("May the force be with you!") } } func main() { s := sample{42} s.useful() s.value = 0 s.useful() } // output: // May the force be with you! // Error: something is wrong! // exit status 1 

I have done a lot of research on how to use interfaces in golang testing. But so far I have not been able to completely wrap my head. At least I don’t see how interfaces help me when I need to β€œmock” (apologize for using this word) golang std. library packages such as "fmt".

I came up with two scenarios:

  • use os / exec to check command line interface
  • wrap fmt , so I have control and I can check the output lines

I do not like both scenarios:

  • It seems to me that the real command line is confusing and non-executive (see below). Portability problems may occur.
  • I believe this is the way to go, but I'm afraid that packaging the fmt package can be a lot of work (at least packaging the time package for testing turned out to be a non-trivial task ( https://github.com/finklabs/ttime )).

Actual question: is there another (better / simpler / idiomatic) way? Note. I want to do this in pure golang, I am not interested in the following testing platform.

cli_test.go:

 package main import( "os/exec" "testing" ) func TestCli(t *testing.T) { out, err := exec.Command("go run sample.go").Output() if err != nil { t.Fatal(err) } if string(out) != "May the force be with you!\nError: this is broken and not useful!\nexit status 1" { t.Fatal("There is something wrong with the CLI") } } 
+7
go testing mocking
source share
2 answers

Chapter 11 of Kearningham's book provides a good solution to this question. The trick is to change the calls on fmt.Printline () to the calls fmt.Fprint (out, ...), where out is initialized by os.Stdout

This can be overwritten in the test wiring to a new one (bytes.Buffer), which allows the test to capture output.

See https://github.com/adonovan/gopl.io/blob/master/ch11/echo/echo.go and https://github.com/adonovan/gopl.io/blob/master/ch11/echo/ echo_test.go

edited OP ... sample.go:

 package main import( "fmt" "os" "io" ) var out io.Writer = os.Stdout // modified during testing var exit func(code int) = os.Exit type sample struct { value int64 } func (s sample) useful() { if s.value == 0 { fmt.Fprint(out, "Error: something is wrong!\n") exit(1) } else { fmt.Fprint(out, "May the force be with you!\n") } } func main() { s := sample{42} s.useful() s.value = 0 s.useful() } // output: // May the force be with you! // Error: this is broken and not useful! // exit status 1 

cli_test.go:

 package main import( "bytes" "testing" ) func TestUsefulPositive(t *testing.T) { bak := out out = new(bytes.Buffer) defer func() { out = bak }() s := sample{42} s.useful() if out.(*bytes.Buffer).String() != "May the force be with you!\n" { t.Fatal("There is something wrong with the CLI") } } func TestUsefulNegative(t *testing.T) { bak := out out = new(bytes.Buffer) defer func() { out = bak }() code := 0 osexit := exit exit = func(c int) { code = c } defer func() { exit = osexit }() s := sample{0} s.useful() if out.(*bytes.Buffer).String() != "Error: something is wrong!\n" { t.Fatal("There is something wrong with the CLI") } if code != 1 { t.Fatal("Wrong exit code!") } } 
+8
source share

Am I missing something or are you talking testable examples ?

Basically, it works as follows: in the *_test.go file, you need to adhere to the agreement Example[[T][_M]] , where T is a placeholder for a type and M placeholder for the method that you want to display the tested example as an example code in Godoc. If the function is simply called Example() , the code will be shown as an example package.

Below the last line of code in your example, you can put a comment like this

 // Output: // Foo 

Now go test will verify that the test case functions as it should , place everything below // Output: (including spaces) or it will cause the test to fail.

Here is an example for a verified example.

 func ExampleMongoStore_Get() { sessionId := "ExampleGetSession" data, err := ms.Get(sessionId) if err == sessionmw.ErrSessionNotFound { fmt.Printf("Session '%s' not found\n", sessionId) data = make(map[string]interface{}) data["foo"] = "bar" ms.Save(sessionId, data) } loaded, _ := ms.Get(sessionId) fmt.Printf("Loaded value '%s' for key '%s' in session '%s'", loaded["foo"], "foo", sessionId) // Output: // Session 'ExampleGetSession' not found // Loaded value 'bar' for key 'foo' in session 'ExampleGetSession' } 

Edit: see the output of the above example on godoc.org

+2
source share

All Articles