This is a short example showing how to use an interface to ease testing, and how to use an interface with running shell commands / other programs and providing mock output.

Here is our main file that actually runs the commands and prints out “hello”.

1 package main 2 3 import ( 4 "fmt" 5 "os/exec" 6 ) 7 8 // first argument is the command, like cat or echo, 9 // the second is the list of args to pass to it 10 type Runner interface { 11 Run ( string , ... string ) ([] byte , error ) 12 } 13 14 type RealRunner struct {} 15 16 var runner Runner 17 18 // the real runner for the actual program, actually execs the command 19 func ( r RealRunner ) Run ( command string , args ... string ) ([] byte , error ) { 20 out , err := exec . Command ( command , args ... ). CombinedOutput () 21 return out , err 22 } 23 24 func Hello () string { 25 out , err := runner . Run ( "echo" , "hello" ) 26 if err != nil { 27 panic ( err ) 28 } 29 return string ( out ) 30 } 31 32 func main () { 33 runner = RealRunner {} 34 fmt . Println ( Hello ()) 35 }

Here is our test file. We start by defining our TestRunner type and implementing the Run(...) interface for it.

This function builds up a command to run the current test file and run the TestHelperProcess function passing along all the args you originally sent. This lets you do things like return different output for different commands you want to run.

The TestHelperProcess function exits when run in the context of the test file, but runs when specified in the files arguments.

1 package main 2 3 import ( 4 "fmt" 5 "os" 6 "os/exec" 7 "testing" 8 ) 9 10 type TestRunner struct {} 11 12 func ( r TestRunner ) Run ( command string , args ... string ) ([] byte , error ) { 13 cs := [] string { "-test.run=TestHelperProcess" , "--" } 14 cs = append ( cs , args ... ) 15 cmd := exec . Command ( os . Args [ 0 ], cs ... ) 16 cmd . Env = [] string { "GO_WANT_HELPER_PROCESS=1" } 17 out , err := cmd . CombinedOutput () 18 return out , err 19 } 20 21 func TestHello ( t * testing . T ) { 22 runner = TestRunner {} 23 out := Hello () 24 if out == "testing helper process" { 25 t . Logf ( "out was eq to %s" , string ( out )) 26 } 27 } 28 29 func TestHelperProcess ( * testing . T ) { 30 if os . Getenv ( "GO_WANT_HELPER_PROCESS" ) != "1" { 31 return 32 } 33 defer os . Exit ( 0 ) 34 fmt . Println ( "testing helper process" ) 35 }

Hopefully this helps someone else! I had a hard time finding some good, short examples on the internet that combined both interfaces and mocking like this.