I tried to unit test connection to MongoDB using mgo.DialWithInfo
function (in the failure case). mgo.DialWithInfo
does not return an error but panics instead.
I tried to add recovery logic to recover from the panic without success.
My questions are:
mgo.DialWithInfo
not return an error
but panics?Code:
Function
func Connect(mongoDBDialInfo *mgo.DialInfo) error {
log.Infof("connect to MongoDB with %v", mongoDBDialInfo)
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in f", r)
}
}()
mongoSession, err := mgo.DialWithInfo(mongoDBDialInfo)
if err != nil {
log.Errorf("error, cannot connect to MongoDB, %v", err)
msg := fmt.Sprintf("fail, cannot connect to MongoDB, %v", mongoDBDialInfo)
sf := "main.main"
errM := err.Error()
util.LogError(msg, sf, errM)
return err
}
DBSession = mongoSession
return err
}
Unit test
func TestConnect_Fail(t *testing.T) {
cf, err := config.GetConfigInstance()
if err != nil {
t.Errorf("expected, cannot connect to MongoDB but found, %v", err)
}
mongoDBDialInfo := &mgo.DialInfo{
Addrs: []string{cf.Config.MongoDB.Host + "ss"},
Timeout: time.Duration(cf.Config.MongoDB.Timeout) * time.Second,
Database: cf.Config.MongoDB.DBName,
Username: cf.Config.MongoDB.Username,
Password: cf.Config.MongoDB.Password,
}
err = Connect(mongoDBDialInfo)
if err == nil {
t.Errorf("expected, cannot connect to MongoDB but can")
}
}
Why does
mgo.DialWithInfo
not return error but panic?
I don't know. It could be the case that:
or a combination thereof.
Your code doesn't look obviously broken, although I am not a user of the mgo library, and that doesn't mean to say there are no issues communicating with external resources, which cannot be diagnosed by looking at the code. Post the contents of the panic message, which may help us determine exactly where the problem lies.
Why recovery does not work?
It isn't clear from your question, but from your unit test and this comment, it appears recovery is working, but not as you intended because it is not returning an error with the panic's value.
The recover()
call in the deferred statement will recover control of a panicking goroutine, return the value given when the panic was initiated, and resume the normal flow of execution. This is all recover
will do. It won't do anything about errors unless you explicitly write code to do so. (And, for completeness, without a call to recover
somewhere in a deferred function in your call stack, a panic will eventually bubble up and be fatal to your application.)
In this context, panic is working: the application will not exit fatally if a panic occurs during the execution of the Connect
function. However, you eat the panic without doing anything about it or signalling to the caller that an error occurred during the Connect
method.
To report this error, you should recover the panic and produce an error which can be returned from the Connect
function. You can't return a value in the deferred function directly, but you can use a named return value to do so. Example code, skipping non-essential parts of the function body:
func Connect(mongoDBDialInfo *mgo.DialInfo) (err error) {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in f", r)
// Return error from Connect with panic's value
err = fmt.Errorf("%v", r)
}
}()
// rest of function body
}