func getHostname() (string, error) {
host, err := os.Hostname()
if err != nil {
// Untested code-block
return "", err
}
// Don't judge the example. This is just an example.
// Basically, I want to know how to induce error in os.Hostname()
return host, nil
}
The corresponding test for this function is:-
import "testing"
func TestGetHostname(t *testing.T) {
host, err := getHostname()
if err != nil {
t.Errorf("Error executing getHostname(): %s", err)
}
if len(host) < 1 {
t.Errorf("Hostname returned is not proper")
}
}
If I were to make the 100% coverage for this function, I would like induce error in os.Hostname()
so that I can run that if block
too. How can I achieve that?
Is creating an interface and passing it as an argument to function the only way?
You can make the code testable by using dependency injection like this:
// Override for testing
var osHostname = os.Hostname
func getHostname() (string, error) {
host, err := osHostname()
if err != nil {
return "", err
}
return host, nil
}
In your test, you can replace the stub with an error-producing version.
func TestGetHostnameFails(t *testing.T) {
defer func() { osHostname = os.Hostname }()
osHostname = func()(string, error) { return "", errors.New("fail") }
got, err := getHostname()
if err == nil {
t.Errorf("getHostname() = (%v, nil), want error", got)
}
}
There's advantages and disadvantages to using package globals to do dependency injection. The advantages are that it's very simple, and most importantly to me doesn't mess the production code up too much. For those reasons it's my choice when you want to test code in isolation like this. The disadvantages are that you may forget to reset the state in your test, and it doesn't give a good testing api -- so if there's lots of tests that are using this stub (or worse, tests in another package), you may prefer to make the configuration explicit by putting it in a struct
, and making getHostname
a method of that struct.
This is just an example, but I feel compelled to provide a cautionary message about getting 100% test coverage. os.Hostname()
is very unlikely to fail in practice, and it's also very unlikely that your error handling when it fails is wrong. It's much more likely that any dependency injection introduces a bug than it is that this test identifies any real bug.
what happens if your function under test panics?
add panic-checking to your testing code:
package hostname
import "testing"
func TestGetHostname(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("The code did panic")
}
}()
name, err := getHostname()
if err != nil {
t.Errorf("err: %v
", err)
}
if len(name) == 0 {
t.Errorf("Hostname is empty")
}
}
old:
look inside os.hostname()
:
in case of err, it will returns:
return "", NewSyscallError("...", err)
your getHostname()
repeats this again.
let me do it again (just to clarify what i'm talking about):
func getHostname2() (string, error) {
host, err := getHostname() // your getHostname !
if err != nil {
return "", err
}
return host, nil
}
this is extra superfluous, isn't it?
so i think this is enough:
package main
import "os"
import "fmt"
func main() {
name, err := os.Hostname()
if err != nil {
fmt.Printf("err: %v
", err)
return
}
fmt.Println(name)
}
so simple error checking is enough (like this):
package main
import "os"
import "fmt"
func work() {
if name, err := os.Hostname(); err != nil {
fmt.Printf("err: %v
", err)
return
} else {
// do some job ...
fmt.Println(name)
}
}
func main() {
work()
}
i hope this helps.