Tuesday, January 1, 2019


Writing unit tests when the func calls panic()

It's a new year and you've decide to step up your unit testing game.   Life is good as a Go programer, since Go comes with an awesome test package in the standard library - https://golang.org/pkg/testing

This is great.  There's no reason to re-invent the wheel. No really, please don't write yet another test package.  You're not that special and your software isn't super special as well.  Please, I'm begging you to use the standard lib solution. DevOps and every other person that maintains your software in the future will curse your name forever if you decide to roll your own.

So that's settled, you're writing some unit tests using the Go testing package, and you stubble into this function:

func mustExist(varValue string, varName string) {
    if len(varValue) == 0 {
        panic(varName + " not set")
    }
}


You have so many questions at this point.  First, why would someone write this func?  What were they thinking?  Don't they know you should never panic?   Haven't they seen Mat Ryer's presentation: Things in Go I never use   Even though you have all these questions, you realize that you probably should refactor this thing, since it's used a LOT in this thing you're writing unit tests for.

So, how are you going write unit tests for this thing?  The first time you send it bad data, it's going to panic and your tests are going to come to a screeching halt.

func TestPanic(t *testing.T) {
mustExist("", "what the heck")
}

--- FAIL: TestPanic (0.00s)
panic: what the heck not set [recovered]
panic: what the heck not set

Yep, just as we feared.  But don't fret, there's a simple solution to this problem: defer and recover. This is straight from the go wiki:  panic  causes the program stack to begin unwinding and  recover  can stop it.

Now we can stop panicking and just write the unit test:

func Test_mustExist(t * testing.T) {
    tests: = [] struct {
        name string
        varValue string
        varName string
        wantPanic bool
    } {
        {
            name: "mom",
            varValue: "hi",
            varName: "mom",
            wantPanic: false
        }, {
            name: "panic",
            varValue: "",
            varName: "panic",
            wantPanic: true
        },
    }
    for _,
    tt: = range tests {
        t.Run(tt.name, func(t * testing.T) {
            func() {
                defer func() {
                    if err: = recover();
                    err != nil {
                        if !tt.wantPanic {
                            t.Errorf("Unexpected panic")
                        }
                    }
                }()
                mustExist(tt.varValue, tt.varName)
            }()
        })
    }
}



Yep, go is pretty sweet.  Even when you're completely panicked, it provides an elegant clear solution.

Happy New Year.

No comments:

Post a Comment