33

I have a simple function I want to test:

func (t *Thing) print(min_verbosity int, message string) {
    if t.verbosity >= minv {
        fmt.Print(message)
    }
}

But how can I test what the function actually sends to standard output? Test::Output does what I want in Perl. I know I could write all my own boilerplate to do the same in Go (as described here):

orig = os.Stdout
r,w,_ = os.Pipe()
thing.print("Some message")
var buf bytes.Buffer
io.Copy(&buf, r)
w.Close()
os.Stdout = orig
if(buf.String() != "Some message") {
    t.Error("Failure!")
}

But that's a lot of extra work for every single test. I'm hoping there's a more standard way, or perhaps an abstraction library to handle this.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189

3 Answers3

51

One thing to also remember, there's nothing stopping you from writing functions to avoid the boilerplate.

For example I have a command line app that uses log and I wrote this function:

func captureOutput(f func()) string {
    var buf bytes.Buffer
    log.SetOutput(&buf)
    f()
    log.SetOutput(os.Stderr)
    return buf.String()
}

Then used it like this:

output := captureOutput(func() {
    client.RemoveCertificate("www.example.com")
})
assert.Equal(t, "removed certificate www.example.com\n", output)

Using this assert library: http://godoc.org/github.com/stretchr/testify/assert.

030
  • 10,842
  • 12
  • 78
  • 123
Caleb
  • 9,272
  • 38
  • 30
  • This is probably what I will do, as it's a more general solution than that provided by @Ainar-G, which requires that I control all of the code which might output something. – Jonathan Hall Nov 07 '14 at 17:15
  • 2
    FYI this is not go-routine safe. A second go-routine could reset log back to `os.Stderr` while the first go-routing is still logging. – colm.anseo Oct 22 '19 at 13:29
  • 2
    FYI: This will only capture `log` outputs and not `fmt` outputs. – Benjamin Haegenlaeuer Jun 28 '21 at 11:19
  • For testing concurrent actions that have log output, this can end up in a race between writing to the buffer and reading from it. For a solution, see my answer here: https://stackoverflow.com/a/68807754/7655137 – ddrake12 Aug 16 '21 at 18:42
21

You can do one of three things. The first is to use Examples.

The package also runs and verifies example code. Example functions may include a concluding line comment that begins with "Output:" and is compared with the standard output of the function when the tests are run. (The comparison ignores leading and trailing space.) These are examples of an example:

func ExampleHello() {
        fmt.Println("hello")
        // Output: hello
}

The second (and more appropriate, IMO) is to use fake functions for your IO. In your code you do:

var myPrint = fmt.Print

func (t *Thing) print(min_verbosity int, message string) {
    if t.verbosity >= minv {
        myPrint(message) // N.B.
    }
}

And in your tests:

func init() {
    myPrint = fakePrint // fakePrint records everything it's supposed to print.
}

func Test...

The third is to use fmt.Fprintf with an io.Writer that is os.Stdout in production code, but bytes.Buffer in tests.

Lodewijk Bogaards
  • 19,777
  • 3
  • 28
  • 52
Ainar-G
  • 34,563
  • 13
  • 93
  • 119
  • 1
    Examples are good for when you have deterministic output and fmt.Fprint is good for when you can control the print code (which you can't if it's in an external library or something). I think most of the time one of these things will be true. If there's a case where neither is true an option to read what is written to os.Stdout into a string would be nice. – voutasaurus Oct 11 '15 at 17:23
  • I actually used a combination of option 2 and 3. First I made a printer interface, which is used throughout my codebase. This makes testing my code easy and gives me full control over what goes to the screen. Second I test my printer, which can print various complicated types of output to stdout and stderr, using option 3. For unit testing my printer I simply give it two `bytes.Buffer` writers instead of `os.Stdout` and `os.StdErr`. – Lodewijk Bogaards Dec 02 '21 at 15:09
  • How do you do number 3? – Quolonel Questions May 02 '22 at 21:57
0

You could consider adding a return statement to your function to return the string that is actually printed out.

func (t *Thing) print(min_verbosity int, message string) string {
    if t.verbosity >= minv {
        fmt.Print(message)
        return message
    }
    return ""
}

Now, your test could just check the returned string against an expected string (rather than the print out). Maybe a bit more in-line with Test Driven Development (TDD).

And, in your production code, nothing would need to change, since you don't have to assign the return value of a function if you don't need it.

Joe Bergevin
  • 3,158
  • 5
  • 26
  • 34