Jonathan Hall
Jonathan Hall

Reputation: 79526

How to test a function's output (stdout/stderr) in unit tests

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.

Upvotes: 38

Views: 38139

Answers (4)

Jeff Learman
Jeff Learman

Reputation: 3267

Adapting the answer from @Caleb to testing and stdout, with some fixes from the OP's code:

func captureOutput(f func() error) (string, error) {
    orig := os.Stdout
    r, w, _ := os.Pipe()
    os.Stdout = w
    err := f()
    os.Stdout = orig
    w.Close()
    out, _ := io.ReadAll(r)
    return string(out), err
}

func TestMyFunc(t *testing.T) {
    output, err := captureOutput(func() error {
        err := myFunc("arg1", "arg2")
        return err
    })
    assert.Nil(t, err)
    assert.Equal(t, "foo", output)
}

Note the location of Close(). Before I moved this above the ReadAll(), the read would hang and cause a test error, for the obvious reason (waiting for more input.)

Upvotes: 5

Ainar-G
Ainar-G

Reputation: 36189

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.

Upvotes: 21

Caleb
Caleb

Reputation: 9458

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.

Upvotes: 53

Joe Bergevin
Joe Bergevin

Reputation: 3268

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.

Upvotes: 1

Related Questions