Go-lang: mocking exec.Command using interfaces
Jun 18, 2014 - 2 minutesThis 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”.
1package main
2
3import (
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
10type Runner interface {
11 Run(string, ...string) ([]byte, error)
12}
13
14type RealRunner struct{}
15
16var runner Runner
17
18// the real runner for the actual program, actually execs the command
19func (r RealRunner) Run(command string, args ...string) ([]byte, error) {
20 out, err := exec.Command(command, args...).CombinedOutput()
21 return out, err
22}
23
24func Hello() string {
25 out, err := runner.Run("echo", "hello")
26 if err != nil {
27 panic(err)
28 }
29 return string(out)
30}
31
32func 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.
1package main
2
3import (
4 "fmt"
5 "os"
6 "os/exec"
7 "testing"
8)
9
10type TestRunner struct{}
11
12func (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
21func 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
29func 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.