11

Is there any way to spy on methods in Golang?

For example, suppose I have

type Object struct { 
    A int
    B string
    C *interface{}
}   

func (o *Object) Something(val interface{}) {
    o.A = 102
    // some other business logic under test

    o.SomethingElse(o.C, val)
}

//...

func (o *Object) Process(val interface{}) interface{} { 
    // some business logic

    return 43 // or something else. no me importa ya
}

//...

func (o *Object) SomethingElse(iPtr *interface{}, val interface{}) { 

    processedVal := o.Process(val)
    if iPtr == nil { 
        iPtr = new(interface{})
    }

    *iPtr = val
}

In writing a test against Object.Something, we should not need to care about what's happening in SomethingElse or the Process invoked from within. Is there any way, in Golang, to isolate these dependencies?

mowzy
  • 105
  • 1
  • 8
Mike Warren
  • 3,796
  • 5
  • 47
  • 99
  • 2
    So, I don't think without using a DI framework such as http://github.com/jwells131313/dargo that you can easily mock (isolate) those dependencies. But if you DO use a DI framework you can have the test mocks be injected rather than the real things from the user code. Even then, the framework can only really mock services, not individual methods – jwells131313 Sep 21 '18 at 15:40
  • 2
    Why did this question get downvoted? It is a legitimate question, about a legitimate testing strategy. – Mike Warren Sep 21 '18 at 19:26
  • 2
    The go community is... uh... grouchy (to put it mildly). See: https://meta.stackoverflow.com/questions/339273/why-is-almost-every-go-question-downvoted – jwells131313 Sep 21 '18 at 21:08
  • YIKES they sound like a bunch of code snobs; /* I HATE people that are like that */ – Mike Warren Sep 22 '18 at 05:12

1 Answers1

7

My understanding of spying is that a single method of an object is faked or mocked while others are not. That's not possible in Go. But you can fake or mock your entire dependencies using interfaces.

In order to fake the SomethingElse method you would have to put it on another struct separate from your Object struct. The Object struct would also need an interface with the SomethingElse method.

type Object struct {
    A     int
    elser Elser
}

func (o *Object) Something() {
    // o.A = 102
    o.elser.SomethingElse()
}

type Elser interface {
    SomethingElse()
}

Now you can test the Object behavior by creating a fake that implements the SomethingElse method:

type fakeElser struct {
    somethingElseWasCalled bool
}

func (f *fakeElser) SomethingElse() {
    f.somethingElseWasCalled = true
}

func TestSomething(t *testing.T) {
    fake := &fakeElser{}
    o := &Object{
        elser: fake,
    }
    o.Something()

    if o.A != 102 {
        t.Errorf("expected A to be %d", 102)
    }

    if !fake.somethingElseWasCalled {
        t.Error("expected SomethingElse to be called")
    }
}

https://play.golang.org/p/RdmbM6Sj5qU

mowzy
  • 105
  • 1
  • 8